diff --git a/About/About.xml b/About/About.xml index a756de2..f1f8d64 100644 --- a/About/About.xml +++ b/About/About.xml @@ -3,67 +3,19 @@ Research Tree Fluffy https://ludeon.com/forums/index.php?topic=16120 - A better research tree. + This is a unofficial update of Fluffy's Research Tree to make it work with Rimworld 1.3 and beyond. Will get removed upon Fluffy's request or when the original mod gets updated. +My fork of this mod: +https://github.com/Zemogiter/ResearchTree/tree/1.3 +Original mod: +https://steamcommunity.com/sharedfiles/filedetails/?id=1266570759 -<size=24>Features</size> - -automatically generated to maximize readability*. -shows research projects, buildings, plants and recipes unlocked by each research project. -projects can be queued, and colonists will automatically start the next project when the current research project completes. -search functionality to quickly find research projects. - -<size=24>FAQ</size> -<i>Can I add/remove this from an existing save?</i> -You can add it to existing saves without problems. Removing this mod will lead to some errors when loading, but these should not affect gameplay - and will go away after saving. - -<i>Why is research X in position Y?</i> -Honestly, I have no idea. The placement of projects (nodes) is automated to minimize the number of crossings between dependancies (edges), and reduce the total length of these edges. There are many possible scenarios in which this can lead to placements that may appear non-optimal. Sometimes they really are non-optimal, sometimes they just appear to be so. See also the <i>technical</i> section below for more information. - -<i>Can I use this with mod X</i> -Most likely, yes. Added researches and their requirements are automatically parsed and the tree layout will be updated accordingly. ResearchPal implements a lot of the same functionality as this mod, and the research queue will likely not work correctly if both mods are loaded. - -<i>This looks very similar to ResearchPal</i> -Yep. ResearchPal is based on a legacy version of this mod that was kept up-to-date by SkyArkAngel in the HCSK modpack. I haven’t worked on this mod in a long time, but I recently had some spare time and decided to give it another go. Feel free to use whichever you like better (ResearchPal has an entirely different layout algorithm). You can run both mods side by side to check out the different tree layouts, but be aware that the research queue will not work correctly if both mods are loaded. - -<size=24>Known Issues</size> - -Layouts are not perfect, if you have experience with graph layouts - please do feel free to look at the source code, and/or implement a Sugiyama layout algorithm for me that runs in C# .NET 3.5 (Mono 2.0). - -<size=24>Technical</size> -So how does this all work? - -Creating an optimal layout is a known problem in the area of <i>Graph Theory</i>. There’s serious mathematicians who’ve spent years of their live trying to figure out this problem, and numerous solutions exist. The group of solutions most relevant to our research tree (a <i>directed acyclic graph</i>, or <i>DAG</i>) is that derived from Sugiyama’s work. Generally speaking, these algorithms have four steps; - - -layering (set the <i>x</i> coordinates of nodes, enforcing that follow-up research is always at a higher x position than any of its prerequisites, this is a fairly straightforward heuristic) -crossing reduction (set the <i>y</i> coordinates of nodes such that there is a minimal amount of intersections of connections between nodes) -edge length reduction (set the <i>y</i> coordinates of nodes such that the length of connections between nodes is minimal) -horizontal alignment (set the <i>y</i> coordinates of nodes such that the connections between nodes are straight as much as possible) - -The final step is the hardest, but also the most important to create a visually pleasing tree. Sadly, I’ve been unable to implement two of the most well known algorithms for this purpose; - - -Brandes, U., & Köpf, B. (2001, September). Fast and simple horizontal coordinate assignment. -Eiglsperger M., Siebenhaller M., Kaufmann M. (2005) An Efficient Implementation of Sugiyama’s Algorithm for Layered Graph Drawing. -Luckily, the crossing reduction and edge length reduction steps partially achieve the goals of the final step. The final graph is not as pretty as it could be, but it’s still pretty good - in most scenarios. - -<size=24>Contributors</size> - -Templarr: Russian translation -Suh. Junmin: Korean translation -rw-chaos: German translation -53N4: Spanish translation -Silverside: Fix UI scaling bug for vertical text -shiuanyue: Chinese (traditional) translation -notfood: Implement techprint requirements -HanYaodong: Add simplified Chinese translation - -<size=24>Version</size> -This is version 3.17.534, for RimWorld 1.1.2654. +Please give me feedback on how to further improve this mod.
  • 1.1
  • +
  • 1.3
  • +
  • 1.4
  • fluffy.researchtree diff --git a/About/PublishedFileId.txt b/About/PublishedFileId.txt new file mode 100644 index 0000000..b86c1f2 --- /dev/null +++ b/About/PublishedFileId.txt @@ -0,0 +1 @@ +2608058938 \ No newline at end of file diff --git a/Assemblies/0Harmony.dll b/Assemblies/0Harmony.dll new file mode 100644 index 0000000..86fc5eb Binary files /dev/null and b/Assemblies/0Harmony.dll differ diff --git a/Assemblies/0Harmony.xml b/Assemblies/0Harmony.xml new file mode 100644 index 0000000..d3eaa4c --- /dev/null +++ b/Assemblies/0Harmony.xml @@ -0,0 +1,3693 @@ + + + + 0Harmony + + + + A factory to create delegate types + + + Default constructor + + + Creates a delegate type for a method + The method + The new delegate type + + + + A getter delegate type + Type that getter gets field/property value from + Type of the value that getter gets + The instance get getter uses + An delegate + + + + A setter delegate type + Type that setter sets field/property value for + Type of the value that setter sets + The instance the setter uses + The value the setter uses + An delegate + + + + A constructor delegate type + Type that constructor creates + An delegate + + + + A helper class for fast access to getters and setters + + + Creates an instantiation delegate + Type that constructor creates + The new instantiation delegate + + + + Creates an getter delegate for a property + Type that getter reads property from + Type of the property that gets accessed + The property + The new getter delegate + + + + Creates an getter delegate for a field + Type that getter reads field from + Type of the field that gets accessed + The field + The new getter delegate + + + + Creates an getter delegate for a field (with a list of possible field names) + Type that getter reads field/property from + Type of the field/property that gets accessed + A list of possible field names + The new getter delegate + + + + Creates an setter delegate + Type that setter assigns property value to + Type of the property that gets assigned + The property + The new setter delegate + + + + Creates an setter delegate for a field + Type that setter assigns field value to + Type of the field that gets assigned + The field + The new getter delegate + + + + A delegate to invoke a method + The instance + The method parameters + The method result + + + A helper class to invoke method with delegates + + + Creates a fast invocation handler from a method + The method to invoke + Controls if boxed value object is accessed/updated directly + The + + + The directBoxValueAccess option controls how value types passed by reference (e.g. ref int, out my_struct) are handled in the arguments array + passed to the fast invocation handler. + Since the arguments array is an object array, any value types contained within it are actually references to a boxed value object. + Like any other object, there can be other references to such boxed value objects, other than the reference within the arguments array. + For example, + + var val = 5; + var box = (object)val; + var arr = new object[] { box }; + handler(arr); // for a method with parameter signature: ref/out/in int + + + + + If directBoxValueAccess is true, the boxed value object is accessed (and potentially updated) directly when the handler is called, + such that all references to the boxed object reflect the potentially updated value. + In the above example, if the method associated with the handler updates the passed (boxed) value to 10, both box and arr[0] + now reflect the value 10. Note that the original val is not updated, since boxing always copies the value into the new boxed value object. + + + If directBoxValueAccess is false (default), the boxed value object in the arguments array is replaced with a "reboxed" value object, + such that potential updates to the value are reflected only in the arguments array. + In the above example, if the method associated with the handler updates the passed (boxed) value to 10, only arr[0] now reflects the value 10. + + + + + A low level memory helper + + + + Mark method for no inlining (currently only works on Mono) + The method/constructor to change + + + + Detours a method + The original method/constructor + The replacement method/constructor + An error string + + + + Writes a jump to memory + The memory address + Jump destination + An error string + + + + Gets the start of a method in memory + The method/constructor + [out] Details of the exception + The method start address + + + + special parameter names that can be used in prefix and postfix methods + + + Patch function helpers + + + Sorts patch methods by their priority rules + The original method + Patches to sort + Use debug mode + The sorted patch methods + + + + Creates new replacement method with the latest patches and detours the original method + The original method + Information describing the patches + The newly created replacement method + + + + Creates a patch sorter + Array of patches that will be sorted + Use debugging + + + Sorts internal PatchSortingWrapper collection and caches the results. + After first run the result is provided from the cache. + The original method + The sorted patch methods + + + Checks if the sorter was created with the same patch list and as a result can be reused to + get the sorted order of the patches. + List of patches to check against + true if equal + + + Removes one unresolved dependency from the least important patch. + + + Outputs all unblocked patches from the waiting list to results list + + + Adds patch to both results list and handled patches set + Patch to add + + + Wrapper used over the Patch object to allow faster dependency access and + dependency removal in case of cyclic dependencies + + + Create patch wrapper object used for sorting + Patch to wrap + + + Determines how patches sort + The other patch + integer to define sort order (-1, 0, 1) + + + Determines whether patches are equal + The other patch + true if equal + + + Hash function + A hash code + + + Bidirectionally registers Patches as after dependencies + List of dependencies to register + + + Bidirectionally registers Patches as before dependencies + List of dependencies to register + + + Bidirectionally removes Patch from after dependencies + Patch to remove + + + Bidirectionally removes Patch from before dependencies + Patch to remove + + + Specifies the type of method + + + + This is a normal method + + + This is a getter + + + This is a setter + + + This is a constructor + + + This is a static constructor + + + This targets the MoveNext method of the enumerator result + + + Specifies the type of argument + + + + This is a normal argument + + + This is a reference argument (ref) + + + This is an out argument (out) + + + This is a pointer argument (&) + + + Specifies the type of patch + + + + Any patch + + + A prefix patch + + + A postfix patch + + + A transpiler + + + A finalizer + + + A reverse patch + + + Specifies the type of reverse patch + + + + Use the unmodified original method (directly from IL) + + + Use the original as it is right now including previous patches but excluding future ones + + + Specifies the type of method call dispatching mechanics + + + + Call the method using dynamic dispatching if method is virtual (including overriden) + + + This is the built-in form of late binding (a.k.a. dynamic binding) and is the default dispatching mechanic in C#. + This directly corresponds with the instruction. + + + For virtual (including overriden) methods, the instance type's most-derived/overriden implementation of the method is called. + For non-virtual (including static) methods, same behavior as : the exact specified method implementation is called. + + + Note: This is not a fully dynamic dispatch, since non-virtual (including static) methods are still called non-virtually. + A fully dynamic dispatch in C# involves using + the dynamic type + (actually a fully dynamic binding, since even the name and overload resolution happens at runtime), which does not support. + + + + + Call the method using static dispatching, regardless of whether method is virtual (including overriden) or non-virtual (including static) + + + a.k.a. non-virtual dispatching, early binding, or static binding. + This directly corresponds with the instruction. + + + For both virtual (including overriden) and non-virtual (including static) methods, the exact specified method implementation is called, without virtual/override mechanics. + + + + + The base class for all Harmony annotations (not meant to be used directly) + + + + The common information for all attributes + + + Annotation to define your Harmony patch methods + + + + An empty annotation can be used together with TargetMethod(s) + + + + An annotation that specifies a class to patch + The declaring class/type + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The argument types of the method or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + An array of argument types to target overloads + Array of + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + An array of argument types to target overloads + Array of + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + The + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + An array of argument types to target overloads + An array of + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + The + + + + An annotation that specifies a method, property or constructor to patch + The + + + + An annotation that specifies a method, property or constructor to patch + The + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The + An array of argument types to target overloads + An array of + + + + An annotation that specifies a method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + An array of argument types to target overloads + An array of + + + + An annotation that specifies a method, property or constructor to patch + The full name of the declaring class/type + The name of the method, property or constructor to patch + The + + + + Annotation to define the original method for delegate injection + + + + An annotation that specifies a class to patch + The declaring class/type + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The argument types of the method or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + An array of argument types to target overloads + Array of + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The + An array of argument types to target overloads + Array of + + + + An annotation that specifies a method, property or constructor to patch + The declaring class/type + The name of the method, property or constructor to patch + The + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + An array of argument types to target overloads + An array of + + + + An annotation that specifies a method, property or constructor to patch + The name of the method, property or constructor to patch + The + + + + An annotation that specifies call dispatching mechanics for the delegate + The + + + + An annotation that specifies a method, property or constructor to patch + The + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + The + An array of argument types to target overloads + An array of + + + + An annotation that specifies a method, property or constructor to patch + An array of argument types to target overloads + + + + An annotation that specifies a method, property or constructor to patch + An array of argument types to target overloads + An array of + + + + Annotation to define your standin methods for reverse patching + + + + An annotation that specifies the type of reverse patching + The of the reverse patch + + + + A Harmony annotation to define that all methods in a class are to be patched + + + + A Harmony annotation + + + + A Harmony annotation to define patch priority + The priority + + + + A Harmony annotation + + + + A Harmony annotation to define that a patch comes before another patch + The array of harmony IDs of the other patches + + + + A Harmony annotation + + + A Harmony annotation to define that a patch comes after another patch + The array of harmony IDs of the other patches + + + + A Harmony annotation + + + A Harmony annotation to debug a patch (output uses to log to your Desktop) + + + + Specifies the Prepare function in a patch class + + + + Specifies the Cleanup function in a patch class + + + + Specifies the TargetMethod function in a patch class + + + + Specifies the TargetMethods function in a patch class + + + + Specifies the Prefix function in a patch class + + + + Specifies the Postfix function in a patch class + + + + Specifies the Transpiler function in a patch class + + + + Specifies the Finalizer function in a patch class + + + + A Harmony annotation + + + + The name of the original argument + + + + The index of the original argument + + + + The new name of the original argument + + + + An annotation to declare injected arguments by name + + + + An annotation to declare injected arguments by index + Zero-based index + + + + An annotation to declare injected arguments by renaming them + Name of the original argument + New name + + + + An annotation to declare injected arguments by index and renaming them + Zero-based index + New name + + + + An abstract wrapper around OpCode and their operands. Used by transpilers + + + + The opcode + + + + The operand + + + + All labels defined on this instruction + + + + All exception block boundaries defined on this instruction + + + + Creates a new CodeInstruction with a given opcode and optional operand + The opcode + The operand + + + + Create a full copy (including labels and exception blocks) of a CodeInstruction + The to copy + + + + Clones a CodeInstruction and resets its labels and exception blocks + A lightweight copy of this code instruction + + + + Clones a CodeInstruction, resets labels and exception blocks and sets its opcode + The opcode + A copy of this CodeInstruction with a new opcode + + + + Clones a CodeInstruction, resets labels and exception blocks and sets its operand + The operand + A copy of this CodeInstruction with a new operand + + + + Creates a CodeInstruction calling a method (CALL) + The class/type where the method is declared + The name of the method (case sensitive) + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A code instruction that calls the method matching the arguments + + + + Creates a CodeInstruction calling a method (CALL) + The target method in the form TypeFullName:MethodName, where the type name matches a form recognized by Type.GetType like Some.Namespace.Type. + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A code instruction that calls the method matching the arguments + + + + Creates a CodeInstruction calling a method (CALL) + The lambda expression using the method + + + + + Creates a CodeInstruction calling a method (CALL) + The lambda expression using the method + + + + + Creates a CodeInstruction calling a method (CALL) + The lambda expression using the method + + + + + Creates a CodeInstruction calling a method (CALL) + The lambda expression using the method + + + + + Returns an instruction to call the specified closure + The delegate type to emit + The closure that defines the method to call + A that calls the closure as a method + + + + Creates a CodeInstruction loading a field (LD[S]FLD[A]) + The class/type where the field is defined + The name of the field (case sensitive) + Use address of field + + + + Creates a CodeInstruction storing to a field (ST[S]FLD) + The class/type where the field is defined + The name of the field (case sensitive) + + + + Returns a string representation of the code instruction + A string representation of the code instruction + + + + Exception block types + + + + The beginning of an exception block + + + + The beginning of a catch block + + + + The beginning of an except filter block (currently not supported to use in a patch) + + + + The beginning of a fault block + + + + The beginning of a finally block + + + + The end of an exception block + + + + An exception block + + + + Block type + + + + Catch type + + + + Creates an exception block + The + The catch type + + + + The Harmony instance is the main entry to Harmony. After creating one with an unique identifier, it is used to patch and query the current application domain + + + + The unique identifier + + + + Set to true before instantiating Harmony to debug Harmony or use an environment variable to set HARMONY_DEBUG to '1' like this: cmd /C "set HARMONY_DEBUG=1 && game.exe" + This is for full debugging. To debug only specific patches, use the attribute + + + + Creates a new Harmony instance + A unique identifier (you choose your own) + A Harmony instance + + + + Searches the current assembly for Harmony annotations and uses them to create patches + This method can fail to use the correct assembly when being inlined. It calls StackTrace.GetFrame(1) which can point to the wrong method/assembly. If you are unsure or run into problems, use PatchAll(Assembly.GetExecutingAssembly()) instead. + + + + Creates a empty patch processor for an original method + The original method/constructor + A new instance + + + + Creates a patch class processor from an annotated class + The class/type + A new instance + + + + Creates a reverse patcher for one of your stub methods + The original method/constructor + The stand-in stub method as + A new instance + + + + Searches an assembly for Harmony annotations and uses them to create patches + The assembly + + + + Creates patches by manually specifying the methods + The original method/constructor + An optional prefix method wrapped in a object + An optional postfix method wrapped in a object + An optional transpiler method wrapped in a object + An optional finalizer method wrapped in a object + The replacement method that was created to patch the original method + + + + Patches a foreign method onto a stub method of yours and optionally applies transpilers during the process + The original method/constructor you want to duplicate + Your stub method as that will become the original. Needs to have the correct signature (either original or whatever your transpilers generates) + An optional transpiler as method that will be applied during the process + The replacement method that was created to patch the stub method + + + + Unpatches methods by patching them with zero patches. Fully unpatching is not supported. Be careful, unpatching is global + The optional Harmony ID to restrict unpatching to a specific Harmony instance + This method could be static if it wasn't for the fact that unpatching creates a new replacement method that contains your harmony ID + + + + Unpatches a method by patching it with zero patches. Fully unpatching is not supported. Be careful, unpatching is global + The original method/constructor + The + The optional Harmony ID to restrict unpatching to a specific Harmony instance + + + + Unpatches a method by patching it with zero patches. Fully unpatching is not supported. Be careful, unpatching is global + The original method/constructor + The patch method as method to remove + + + + Test for patches from a specific Harmony ID + The Harmony ID + True if patches for this ID exist + + + + Gets patch information for a given original method + The original method/constructor + The patch information as + + + + Gets the methods this instance has patched + An enumeration of original methods/constructors + + + + Gets all patched original methods in the appdomain + An enumeration of patched original methods/constructors + + + + Gets the original method from a given replacement method + A replacement method, for example from a stacktrace + The original method/constructor or null if not found + + + + Tries to get the method from a stackframe including dynamic replacement methods + The + For normal frames, frame.GetMethod() is returned. For frames containing patched methods, the replacement method is returned or null if no method can be found + + + + Gets the original method from the stackframe and uses original if method is a dynamic replacement + The + The original method from that stackframe + + + Gets Harmony version for all active Harmony instances + [out] The current Harmony version + A dictionary containing assembly versions keyed by Harmony IDs + + + + Under Mono, HarmonyException wraps IL compile errors with detailed information about the failure + + + + Default serialization constructor (not implemented) + The info + The context + + + + Get a list of IL instructions in pairs of offset+code + A list of key/value pairs which represent an offset and the code at that offset + + + + Get a list of IL instructions without offsets + A list of + + + + Get the error offset of the errornous IL instruction + The offset + + + + Get the index of the errornous IL instruction + The index into the list of instructions or -1 if not found + + + + A wrapper around a method to use it as a patch (for example a Prefix) + + + + The original method + + + + Class/type declaring this patch + + + + Patch method name + + + + Optional patch + + + + Array of argument types of the patch method + + + + of the patch + + + + Install this patch before patches with these Harmony IDs + + + + Install this patch after patches with these Harmony IDs + + + + Reverse patch type, see + + + + Create debug output for this patch + + + + Whether to use (true) or (false) mechanics + for -attributed delegate + + + + Default constructor + + + + Creates a patch from a given method + The original method + + + + Creates a patch from a given method + The original method + The patch + A list of harmony IDs that should come after this patch + A list of harmony IDs that should come before this patch + Set to true to generate debug output + + + + Creates a patch from a given method + The patch class/type + The patch method name + The optional argument types of the patch method (for overloaded methods) + + + + Gets the names of all internal patch info fields + A list of field names + + + + Merges annotations + The list of to merge + The merged + + + + Returns a string that represents the annotation + A string representation + + + + Annotation extensions + + + + Copies annotation information + The source + The destination + + + + Clones an annotation + The to clone + A copied + + + + Merges annotations + The master + The detail + A new, merged + + + + Gets all annotations on a class/type + The class/type + A list of all + + + + Gets merged annotations on a class/type + The class/type + The merged + + + + Gets all annotations on a method + The method/constructor + A list of + + + + Gets merged annotations on a method + The method/constructor + The merged + + + + + A mutable representation of an inline signature, similar to Mono.Cecil's CallSite. + Used by the calli instruction, can be used by transpilers + + + + + See + + + + See + + + + See + + + + The list of all parameter types or function pointer signatures received by the call site + + + + The return type or function pointer signature returned by the call site + + + + Returns a string representation of the inline signature + A string representation of the inline signature + + + + + A mutable representation of a parameter type with an attached type modifier, + similar to Mono.Cecil's OptionalModifierType / RequiredModifierType and C#'s modopt / modreq + + + + + Whether this is a modopt (optional modifier type) or a modreq (required modifier type) + + + + The modifier type attached to the parameter type + + + + The modified parameter type + + + + Returns a string representation of the modifier type + A string representation of the modifier type + + + + Patch serialization + + + + Control the binding of a serialized object to a type + Specifies the assembly name of the serialized object + Specifies the type name of the serialized object + The type of the object the formatter creates a new instance of + + + + Serializes a patch info + The + The serialized data + + + + Deserialize a patch info + The serialized data + A + + + + Compare function to sort patch priorities + The patch + Zero-based index + The priority + A standard sort integer (-1, 0, 1) + + + + Serializable patch information + + + + Prefixes as an array of + + + + Postfixes as an array of + + + + Transpilers as an array of + + + + Finalizers as an array of + + + + Returns if any of the patches wants debugging turned on + + + + Adds prefixes + An owner (Harmony ID) + The patch methods + + + + Adds a prefix + + + Removes prefixes + The owner of the prefixes, or * for all + + + + Adds postfixes + An owner (Harmony ID) + The patch methods + + + + Adds a postfix + + + Removes postfixes + The owner of the postfixes, or * for all + + + + Adds transpilers + An owner (Harmony ID) + The patch methods + + + + Adds a transpiler + + + Removes transpilers + The owner of the transpilers, or * for all + + + + Adds finalizers + An owner (Harmony ID) + The patch methods + + + + Adds a finalizer + + + Removes finalizers + The owner of the finalizers, or * for all + + + + Removes a patch using its method + The method of the patch to remove + + + + Gets a concatenated list of patches + The Harmony instance ID adding the new patches + The patches to add + The current patches + + + + Gets a list of patches with any from the given owner removed + The owner of the methods, or * for all + The current patches + + + + A serializable patch + + + + Zero-based index + + + + The owner (Harmony ID) + + + + The priority, see + + + + Keep this patch before the patches indicated in the list of Harmony IDs + + + + Keep this patch after the patches indicated in the list of Harmony IDs + + + + A flag that will log the replacement method via every time this patch is used to build the replacement, even in the future + + + + The method of the static patch method + + + + Creates a patch + The method of the patch + Zero-based index + An owner (Harmony ID) + The priority, see + A list of Harmony IDs for patches that should run after this patch + A list of Harmony IDs for patches that should run before this patch + A flag that will log the replacement method via every time this patch is used to build the replacement, even in the future + + + + Creates a patch + The method of the patch + Zero-based index + An owner (Harmony ID) + + + Get the patch method or a DynamicMethod if original patch method is a patch factory + The original method/constructor + The method of the patch + + + + Determines whether patches are equal + The other patch + true if equal + + + + Determines how patches sort + The other patch + integer to define sort order (-1, 0, 1) + + + + Hash function + A hash code + + + + A PatchClassProcessor used to turn on a class/type into patches + + + + Creates a patch class processor by pointing out a class. Similar to PatchAll() but without searching through all classes. + The Harmony instance + The class to process (need to have at least a [HarmonyPatch] attribute) + + + + Applies the patches + A list of all created replacement methods or null if patch class is not annotated + + + + A group of patches + + + + A collection of prefix + + + + A collection of postfix + + + + A collection of transpiler + + + + A collection of finalizer + + + + Gets all owners (Harmony IDs) or all known patches + The patch owners + + + + Creates a group of patches + An array of prefixes as + An array of postfixes as + An array of transpileres as + An array of finalizeres as + + + + A PatchProcessor handles patches on a method/constructor + + + + Creates an empty patch processor + The Harmony instance + The original method/constructor + + + + Adds a prefix + The prefix as a + A for chaining calls + + + + Adds a prefix + The prefix method + A for chaining calls + + + + Adds a postfix + The postfix as a + A for chaining calls + + + + Adds a postfix + The postfix method + A for chaining calls + + + + Adds a transpiler + The transpiler as a + A for chaining calls + + + + Adds a transpiler + The transpiler method + A for chaining calls + + + + Adds a finalizer + The finalizer as a + A for chaining calls + + + + Adds a finalizer + The finalizer method + A for chaining calls + + + + Gets all patched original methods in the appdomain + An enumeration of patched method/constructor + + + + Applies all registered patches + The generated replacement method + + + + Unpatches patches of a given type and/or Harmony ID + The patch type + Harmony ID or * for any + A for chaining calls + + + + Unpatches a specific patch + The method of the patch + A for chaining calls + + + + Gets patch information on an original + The original method/constructor + The patch information as + + + + Sort patch methods by their priority rules + The original method + Patches to sort + The sorted patch methods + + + + Gets Harmony version for all active Harmony instances + [out] The current Harmony version + A dictionary containing assembly version keyed by Harmony ID + + + + Creates a new empty generator to use when reading method bodies + A new + + + + Creates a new generator matching the method/constructor to use when reading method bodies + The original method/constructor to copy method information from + A new + + + + Returns the methods unmodified list of code instructions + The original method/constructor + Optionally an existing generator that will be used to create all local variables and labels contained in the result (if not specified, an internal generator is used) + A list containing all the original + + + + Returns the methods unmodified list of code instructions + The original method/constructor + A new generator that now contains all local variables and labels contained in the result + A list containing all the original + + + + Returns the methods current list of code instructions after all existing transpilers have been applied + The original method/constructor + Apply only the first count of transpilers + Optionally an existing generator that will be used to create all local variables and labels contained in the result (if not specified, an internal generator is used) + A list of + + + + Returns the methods current list of code instructions after all existing transpilers have been applied + The original method/constructor + A new generator that now contains all local variables and labels contained in the result + Apply only the first count of transpilers + A list of + + + + A low level way to read the body of a method. Used for quick searching in methods + The original method + All instructions as opcode/operand pairs + + + + A low level way to read the body of a method. Used for quick searching in methods + The original method + An existing generator that will be used to create all local variables and labels contained in the result + All instructions as opcode/operand pairs + + + + A patch priority + + + + Patch last + + + + Patch with very low priority + + + + Patch with low priority + + + + Patch with lower than normal priority + + + + Patch with normal priority + + + + Patch with higher than normal priority + + + + Patch with high priority + + + + Patch with very high priority + + + + Patch first + + + + A reverse patcher + + + + Creates a reverse patcher + The Harmony instance + The original method/constructor + Your stand-in stub method as + + + + Applies the patch + The type of patch, see + The generated replacement method + + + + A collection of commonly used transpilers + + + + A transpiler that replaces all occurrences of a given method with another one using the same signature + The enumeration of to act on + Method or constructor to search for + Method or constructor to replace with + Modified enumeration of + + + + A transpiler that alters instructions that match a predicate by calling an action + The enumeration of to act on + A predicate selecting the instructions to change + An action to apply to matching instructions + Modified enumeration of + + + + A transpiler that logs a text at the beginning of the method + The instructions to act on + The log text + Modified enumeration of + + + + A helper class for reflection related functions + + + + Shortcut for to simplify the use of reflections and make it work for any access level + + + + Shortcut for to simplify the use of reflections and make it work for any access level but only within the current type + + + + Enumerates all assemblies in the current app domain, excluding visual studio assemblies + An enumeration of + + + Gets a type by name. Prefers a full name with namespace but falls back to the first type matching the name otherwise + The name + A type or null if not found + + + + Gets all successfully loaded types from a given assembly + The assembly + An array of types + + This calls and returns , while catching any thrown . + If such an exception is thrown, returns the successfully loaded types (, + filtered for non-null values). + + + + + Enumerates all successfully loaded types in the current app domain, excluding visual studio assemblies + An enumeration of all in all assemblies, excluding visual studio assemblies + + + Applies a function going up the type hierarchy and stops at the first non-null result + Result type of func() + The class/type to start with + The evaluation function returning T + The first non-null result, or null if no match + + The type hierarchy of a class or value type (including struct) does NOT include implemented interfaces, + and the type hierarchy of an interface is only itself (regardless of whether that interface implements other interfaces). + The top-most type in the type hierarchy of all non-interface types (including value types) is . + + + + + Applies a function going into inner types and stops at the first non-null result + Generic type parameter + The class/type to start with + The evaluation function returning T + The first non-null result, or null if no match + + + + Gets the reflection information for a directly declared field + The class/type where the field is defined + The name of the field + A field or null when type/name is null or when the field cannot be found + + + + Gets the reflection information for a directly declared field + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A field or null when the field cannot be found + + + + Gets the reflection information for a field by searching the type and all its super types + The class/type where the field is defined + The name of the field (case sensitive) + A field or null when type/name is null or when the field cannot be found + + + + Gets the reflection information for a field by searching the type and all its super types + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A field or null when the field cannot be found + + + + Gets the reflection information for a field + The class/type where the field is declared + The zero-based index of the field inside the class definition + A field or null when type is null or when the field cannot be found + + + + Gets the reflection information for a directly declared property + The class/type where the property is declared + The name of the property (case sensitive) + A property or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for a directly declared property + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A property or null when the property cannot be found + + + + Gets the reflection information for the getter method of a directly declared property + The class/type where the property is declared + The name of the property (case sensitive) + A method or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for the getter method of a directly declared property + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A method or null when the property cannot be found + + + + Gets the reflection information for the setter method of a directly declared property + The class/type where the property is declared + The name of the property (case sensitive) + A method or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for the Setter method of a directly declared property + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A method or null when the property cannot be found + + + + Gets the reflection information for a property by searching the type and all its super types + The class/type + The name + A property or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for a property by searching the type and all its super types + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A property or null when the property cannot be found + + + + Gets the reflection information for the getter method of a property by searching the type and all its super types + The class/type + The name + A method or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for the getter method of a property by searching the type and all its super types + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A method or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for the setter method of a property by searching the type and all its super types + The class/type + The name + A method or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for the setter method of a property by searching the type and all its super types + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A method or null when type/name is null or when the property cannot be found + + + + Gets the reflection information for a directly declared method + The class/type where the method is declared + The name of the method (case sensitive) + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A method or null when type/name is null or when the method cannot be found + + + + Gets the reflection information for a directly declared method + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A method or null when the method cannot be found + + + + Gets the reflection information for a method by searching the type and all its super types + The class/type where the method is declared + The name of the method (case sensitive) + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A method or null when type/name is null or when the method cannot be found + + + + Gets the reflection information for a method by searching the type and all its super types + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + Optional parameters to target a specific overload of the method + Optional list of types that define the generic version of the method + A method or null when the method cannot be found + + + + Gets the method of an enumerator method + Enumerator method that creates the enumerator + The internal method of the enumerator or null if no valid enumerator is detected + + + Gets the names of all method that are declared in a type + The declaring class/type + A list of method names + + + + Gets the names of all method that are declared in the type of the instance + An instance of the type to search in + A list of method names + + + + Gets the names of all fields that are declared in a type + The declaring class/type + A list of field names + + + + Gets the names of all fields that are declared in the type of the instance + An instance of the type to search in + A list of field names + + + + Gets the names of all properties that are declared in a type + The declaring class/type + A list of property names + + + + Gets the names of all properties that are declared in the type of the instance + An instance of the type to search in + A list of property names + + + + Gets the type of any class member of + A member + The class/type of this member + + + + Test if a class member is actually an concrete implementation + A member + True if the member is a declared + + + + Gets the real implementation of a class member + A member + The member itself if its declared. Otherwise the member that is actually implemented in some base type + + + + Gets the reflection information for a directly declared constructor + The class/type where the constructor is declared + Optional parameters to target a specific overload of the constructor + Optional parameters to only consider static constructors + A constructor info or null when type is null or when the constructor cannot be found + + + + Gets the reflection information for a constructor by searching the type and all its super types + The class/type where the constructor is declared + Optional parameters to target a specific overload of the method + Optional parameters to only consider static constructors + A constructor info or null when type is null or when the method cannot be found + + + + Gets reflection information for all declared constructors + The class/type where the constructors are declared + Optional parameters to only consider static constructors + A list of constructor infos + + + + Gets reflection information for all declared methods + The class/type where the methods are declared + A list of methods + + + + Gets reflection information for all declared properties + The class/type where the properties are declared + A list of properties + + + + Gets reflection information for all declared fields + The class/type where the fields are declared + A list of fields + + + + Gets the return type of a method or constructor + The method/constructor + The return type + + + + Given a type, returns the first inner type matching a recursive search by name + The class/type to start searching at + The name of the inner type (case sensitive) + The inner type or null if type/name is null or if a type with that name cannot be found + + + + Given a type, returns the first inner type matching a recursive search with a predicate + The class/type to start searching at + The predicate to search with + The inner type or null if type/predicate is null or if a type with that name cannot be found + + + + Given a type, returns the first method matching a predicate + The class/type to start searching at + The predicate to search with + The method or null if type/predicate is null or if a type with that name cannot be found + + + + Given a type, returns the first constructor matching a predicate + The class/type to start searching at + The predicate to search with + The constructor info or null if type/predicate is null or if a type with that name cannot be found + + + + Given a type, returns the first property matching a predicate + The class/type to start searching at + The predicate to search with + The property or null if type/predicate is null or if a type with that name cannot be found + + + + Returns an array containing the type of each object in the given array + An array of objects + An array of types or an empty array if parameters is null (if an object is null, the type for it will be object) + + + + Creates an array of input parameters for a given method and a given set of potential inputs + The method/constructor you are planing to call + The possible input parameters in any order + An object array matching the method signature + + + + A readable/assignable reference delegate to an instance field of a class or static field (NOT an instance field of a struct) + + An arbitrary type if the field is static; otherwise the class that defines the field, or a parent class (including ), + implemented interface, or derived class of this type + + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The runtime instance to access the field (ignored and can be omitted for static fields) + A readable/assignable reference to the field + Null instance passed to a non-static field ref delegate + + Instance of invalid type passed to a non-static field ref delegate + (this can happen if is a parent class or interface of the field's declaring type) + + + + This delegate cannot be used for instance fields of structs, since a struct instance passed to the delegate would be passed by + value and thus would be a copy that only exists within the delegate's invocation. This is fine for a readonly reference, + but makes assignment futile. Use instead. + + + Note that is not required to be the field's declaring type. It can be a parent class (including ), + implemented interface, or a derived class of the field's declaring type ("instanceOfT is FieldDeclaringType" must be possible). + Specifically, must be assignable from OR to the field's declaring type. + Technically, this allows Nullable, although Nullable is only relevant for structs, and since only static fields of structs + are allowed for this delegate, and the instance passed to such a delegate is ignored, this hardly matters. + + + Similarly, is not required to be the field's field type, unless that type is a non-enum value type. + It can be a parent class (including object) or implemented interface of the field's field type. It cannot be a derived class. + This variance is not allowed for value types, since that would require boxing/unboxing, which is not allowed for ref values. + Special case for enum types: can also be the underlying integral type of the enum type. + Specifically, for reference types, must be assignable from + the field's field type; for non-enum value types, must be exactly the field's field type; for enum types, + must be either the field's field type or the underyling integral type of that field type. + + + This delegate supports static fields, even those defined in structs, for legacy reasons. + For such static fields, is effectively ignored. + Consider using (and StaticFieldRefAccess methods that return it) instead for static fields. + + + + + + Creates a field reference delegate for an instance field of a class + The class that defines the instance field, or derived class of this type + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The name of the field + A readable/assignable delegate + + + For backwards compatibility, there is no class constraint on . + Instead, the non-value-type check is done at runtime within the method. + + + + + + Creates an instance field reference for a specific instance of a class + The class that defines the instance field, or derived class of this type + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The instance + The name of the field + A readable/assignable reference to the field + + + This method is meant for one-off access to a field's value for a single instance. + If you need to access a field's value for potentially multiple instances, use instead. + FieldRefAccess<T, F>(instance, fieldName) is functionally equivalent to FieldRefAccess<T, F>(fieldName)(instance). + + + For backwards compatibility, there is no class constraint on . + Instead, the non-value-type check is done at runtime within the method. + + + + + + Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct) + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + + The type that defines the field, or derived class of this type; must not be a struct type unless the field is static + + The name of the field + + A readable/assignable delegate with T=object + (for static fields, the instance delegate parameter is ignored) + + + + This method is meant for cases where the given type is only known at runtime and thus can't be used as a type parameter T + in e.g. . + + + This method supports static fields, even those defined in structs, for legacy reasons. + Consider using (and other overloads) instead for static fields. + + + + + + Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct) + type of the field + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A readable/assignable delegate with T=object + + + Creates a field reference delegate for an instance field of a class or static field (NOT an instance field of a struct) + + An arbitrary type if the field is static; otherwise the class that defines the field, or a parent class (including ), + implemented interface, or derived class of this type ("instanceOfT is FieldDeclaringType" must be possible) + + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The field + A readable/assignable delegate + + + This method is meant for cases where the field has already been obtained, avoiding the field searching cost in + e.g. . + + + This method supports static fields, even those defined in structs, for legacy reasons. + For such static fields, is effectively ignored. + Consider using (and other overloads) instead for static fields. + + + For backwards compatibility, there is no class constraint on . + Instead, the non-value-type check is done at runtime within the method. + + + + + + Creates a field reference for an instance field of a class + + The type that defines the field; or a parent class (including ), implemented interface, or derived class of this type + ("instanceOfT is FieldDeclaringType" must be possible) + + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The instance + The field + A readable/assignable reference to the field + + + This method is meant for one-off access to a field's value for a single instance and where the field has already been obtained. + If you need to access a field's value for potentially multiple instances, use instead. + FieldRefAccess<T, F>(instance, fieldInfo) is functionally equivalent to FieldRefAccess<T, F>(fieldInfo)(instance). + + + For backwards compatibility, there is no class constraint on . + Instead, the non-value-type check is done at runtime within the method. + + + + + + A readable/assignable reference delegate to an instance field of a struct + The struct that defines the instance field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + A reference to the runtime instance to access the field + A readable/assignable reference to the field + + + + Creates a field reference delegate for an instance field of a struct + The struct that defines the instance field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The name of the field + A readable/assignable delegate + + + + Creates an instance field reference for a specific instance of a struct + The struct that defines the instance field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The instance + The name of the field + A readable/assignable reference to the field + + + This method is meant for one-off access to a field's value for a single instance. + If you need to access a field's value for potentially multiple instances, use instead. + StructFieldRefAccess<T, F>(ref instance, fieldName) is functionally equivalent to StructFieldRefAccess<T, F>(fieldName)(ref instance). + + + + + + Creates a field reference delegate for an instance field of a struct + The struct that defines the instance field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The field + A readable/assignable delegate + + + This method is meant for cases where the field has already been obtained, avoiding the field searching cost in + e.g. . + + + + + + Creates a field reference for an instance field of a struct + The struct that defines the instance field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The instance + The field + A readable/assignable reference to the field + + + This method is meant for one-off access to a field's value for a single instance and where the field has already been obtained. + If you need to access a field's value for potentially multiple instances, use instead. + StructFieldRefAccess<T, F>(ref instance, fieldInfo) is functionally equivalent to StructFieldRefAccess<T, F>(fieldInfo)(ref instance). + + + + + + A readable/assignable reference delegate to a static field + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + A readable/assignable reference to the field + + + + Creates a static field reference + The type (can be class or struct) the field is defined in + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The name of the field + A readable/assignable reference to the field + + + + Creates a static field reference + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The type (can be class or struct) the field is defined in + The name of the field + A readable/assignable reference to the field + + + + Creates a static field reference + The type of the field + The member in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + A readable/assignable reference to the field + + + + Creates a static field reference + An arbitrary type (by convention, the type the field is defined in) + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The field + A readable/assignable reference to the field + + The type parameter is only used in exception messaging and to distinguish between this method overload + and the overload (which returns a rather than a reference). + + + + + Creates a static field reference delegate + + The type of the field; or if the field's type is a reference type (a class or interface, NOT a struct or other value type), + a type that is assignable from that type; or if the field's type is an enum type, + either that type or the underlying integral type of that enum type + + The field + A readable/assignable delegate + + + + Creates a delegate to a given method + The delegate Type + The method to create a delegate from. + + Only applies for instance methods. If null (default), returned delegate is an open (a.k.a. unbound) instance delegate + where an instance is supplied as the first argument to the delegate invocation; else, delegate is a closed (a.k.a. bound) + instance delegate where the delegate invocation always applies to the given . + + + Only applies for instance methods. If true (default) and is virtual, invocation of the delegate + calls the instance method virtually (the instance type's most-derived/overriden implementation of the method is called); + else, invocation of the delegate calls the exact specified (this is useful for calling base class methods) + Note: if false and is an interface method, an ArgumentException is thrown. + + A delegate of given to given + + + Delegate invocation is more performant and more convenient to use than + at a one-time setup cost. + + + Works for both type of static and instance methods, both open and closed (a.k.a. unbound and bound) instance methods, + and both class and struct methods. + + + + + + Creates a delegate to a given method + The delegate Type + The method in the form TypeFullName:MemberName, where TypeFullName matches the form recognized by Type.GetType like Some.Namespace.Type. + + Only applies for instance methods. If null (default), returned delegate is an open (a.k.a. unbound) instance delegate + where an instance is supplied as the first argument to the delegate invocation; else, delegate is a closed (a.k.a. bound) + instance delegate where the delegate invocation always applies to the given . + + + Only applies for instance methods. If true (default) and is virtual, invocation of the delegate + calls the instance method virtually (the instance type's most-derived/overriden implementation of the method is called); + else, invocation of the delegate calls the exact specified (this is useful for calling base class methods) + Note: if false and is an interface method, an ArgumentException is thrown. + + A delegate of given to given + + + Delegate invocation is more performant and more convenient to use than + at a one-time setup cost. + + + Works for both type of static and instance methods, both open and closed (a.k.a. unbound and bound) instance methods, + and both class and struct methods. + + + + + + Creates a delegate for a given delegate definition, attributed with [] + The delegate Type, attributed with [] + + Only applies for instance methods. If null (default), returned delegate is an open (a.k.a. unbound) instance delegate + where an instance is supplied as the first argument to the delegate invocation; else, delegate is a closed (a.k.a. bound) + instance delegate where the delegate invocation always applies to the given . + + A delegate of given to the method specified via [] + attributes on + + This calls with the method and virtualCall arguments + determined from the [] attributes on , + and the given (for closed instance delegates). + + + + + Returns who called the current method + The calling method/constructor (excluding the caller) + + + + Rethrows an exception while preserving its stack trace (throw statement typically clobbers existing stack traces) + The exception to rethrow + + + + True if the current runtime is based on Mono, false otherwise (.NET) + + + + True if the current runtime is .NET Framework, false otherwise (.NET Core or Mono, although latter isn't guaranteed) + + + + True if the current runtime is .NET Core, false otherwise (Mono or .NET Framework) + + + + Throws a missing member runtime exception + The type that is involved + A list of names + + + + Gets default value for a specific type + The class/type + The default value + + + + Creates an (possibly uninitialized) instance of a given type + The class/type + The new instance + + + + Creates an (possibly uninitialized) instance of a given type + The class/type + The new instance + + + + + A cache for the or similar Add methods for different types. + + + + Makes a deep copy of any object + The type of the instance that should be created; for legacy reasons, this must be a class or interface + The original object + A copy of the original object but of type T + + + + Makes a deep copy of any object + The type of the instance that should be created + The original object + [out] The copy of the original object + Optional value transformation function (taking a field name and src/dst instances) + The optional path root to start with + + + + Makes a deep copy of any object + The original object + The type of the instance that should be created + Optional value transformation function (taking a field name and src/dst instances) + The optional path root to start with + The copy of the original object + + + + Tests if a type is a struct + The type + True if the type is a struct + + + + Tests if a type is a class + The type + True if the type is a class + + + + Tests if a type is a value type + The type + True if the type is a value type + + + + Tests if a type is an integer type + The type + True if the type represents some integer + + + + Tests if a type is a floating point type + The type + True if the type represents some floating point + + + + Tests if a type is a numerical type + The type + True if the type represents some number + + + + Tests if a type is void + The type + True if the type is void + + + + Test whether an instance is of a nullable type + Type of instance + An instance to test + True if instance is of nullable type, false if not + + + + Tests whether a type or member is static, as defined in C# + The type or member + True if the type or member is static + + + + Tests whether a type is static, as defined in C# + The type + True if the type is static + + + + Tests whether a property is static, as defined in C# + The property + True if the property is static + + + + Tests whether an event is static, as defined in C# + The event + True if the event is static + + + + Calculates a combined hash code for an enumeration of objects + The objects + The hash code + + + + A CodeInstruction match + + + The name of the match + + + The matched opcodes + + + The matched operands + + + The jumps from the match + + + The jumps to the match + + + The match predicate + + + Creates a code match + The optional opcode + The optional operand + The optional name + + + + Creates a code match that calls a method + The lambda expression using the method + The optional name + + + + Creates a code match that calls a method + The lambda expression using the method + The optional name + + + + Creates a code match + The CodeInstruction + An optional name + + + + Creates a code match + The predicate + An optional name + + + + Returns a string that represents the match + A string representation + + + + A CodeInstruction matcher + + + The current position + The index or -1 if out of bounds + + + + Gets the number of code instructions in this matcher + The count + + + + Checks whether the position of this CodeMatcher is within bounds + True if this CodeMatcher is valid + + + + Checks whether the position of this CodeMatcher is outside its bounds + True if this CodeMatcher is invalid + + + + Gets the remaining code instructions + The remaining count + + + + Gets the opcode at the current position + The opcode + + + + Gets the operand at the current position + The operand + + + + Gets the labels at the current position + The labels + + + + Gets the exception blocks at the current position + The blocks + + + + Creates an empty code matcher + + + Creates a code matcher from an enumeration of instructions + The instructions (transpiler argument) + An optional IL generator + + + + Makes a clone of this instruction matcher + A copy of this matcher + + + + Gets instructions at the current position + The instruction + + + + Gets instructions at the current position with offset + The offset + The instruction + + + + Gets all instructions + A list of instructions + + + + Gets all instructions as an enumeration + A list of instructions + + + + Gets some instructions counting from current position + Number of instructions + A list of instructions + + + + Gets all instructions within a range + The start index + The end index + A list of instructions + + + + Gets all instructions within a range (relative to current position) + The start offset + The end offset + A list of instructions + + + + Gets a list of all distinct labels + The instructions (transpiler argument) + A list of Labels + + + + Reports a failure + The method involved + The logger + True if current position is invalid and error was logged + + + + Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed) + Explanation of where/why the exception was thrown that will be added to the exception message + The same code matcher + + + + Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed), + or if the matches do not match at current position + Explanation of where/why the exception was thrown that will be added to the exception message + Some code matches + The same code matcher + + + + Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed), + or if the matches do not match at any point between current position and the end + Explanation of where/why the exception was thrown that will be added to the exception message + Some code matches + The same code matcher + + + + Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed), + or if the matches do not match at any point between current position and the start + Explanation of where/why the exception was thrown that will be added to the exception message + Some code matches + The same code matcher + + + + Throw an InvalidOperationException if current state is invalid (position out of bounds / last match failed), + or if the check function returns false + Explanation of where/why the exception was thrown that will be added to the exception message + Function that checks validity of current state. If it returns false, an exception is thrown + The same code matcher + + + + Sets an instruction at current position + The instruction to set + The same code matcher + + + + Sets instruction at current position and advances + The instruction + The same code matcher + + + + Sets opcode and operand at current position + The opcode + The operand + The same code matcher + + + + Sets opcode and operand at current position and advances + The opcode + The operand + The same code matcher + + + + Sets opcode at current position and advances + The opcode + The same code matcher + + + + Sets operand at current position and advances + The operand + The same code matcher + + + + Creates a label at current position + [out] The label + The same code matcher + + + + Creates a label at a position + The position + [out] The new label + The same code matcher + + + + Creates a label at a position + The offset + [out] The new label + The same code matcher + + + + Adds an enumeration of labels to current position + The labels + The same code matcher + + + + Adds an enumeration of labels at a position + The position + The labels + The same code matcher + + + + Sets jump to + Branch instruction + Destination for the jump + [out] The created label + The same code matcher + + + + Inserts some instructions + The instructions + The same code matcher + + + + Inserts an enumeration of instructions + The instructions + The same code matcher + + + + Inserts a branch + The branch opcode + Branch destination + The same code matcher + + + + Inserts some instructions and advances the position + The instructions + The same code matcher + + + + Inserts an enumeration of instructions and advances the position + The instructions + The same code matcher + + + + Inserts a branch and advances the position + The branch opcode + Branch destination + The same code matcher + + + + Removes current instruction + The same code matcher + + + + Removes some instruction from current position by count + Number of instructions + The same code matcher + + + + Removes the instructions in a range + The start + The end + The same code matcher + + + + Removes the instructions in a offset range + The start offset + The end offset + The same code matcher + + + + Advances the current position + The offset + The same code matcher + + + + Moves the current position to the start + The same code matcher + + + + Moves the current position to the end + The same code matcher + + + + Searches forward with a predicate and advances position + The predicate + The same code matcher + + + + Searches backwards with a predicate and reverses position + The predicate + The same code matcher + + + + Matches forward and advances position to beginning of matching sequence + Some code matches + The same code matcher + + + + Matches forward and advances position to ending of matching sequence + Some code matches + The same code matcher + + + + Matches backwards and reverses position to beginning of matching sequence + Some code matches + The same code matcher + + + + Matches backwards and reverses position to ending of matching sequence + Some code matches + The same code matcher + + + + Repeats a match action until boundaries are met + The match action + An optional action that is executed when no match is found + The same code matcher + + + + Gets a match by its name + The match name + An instruction + + + + General extensions for common cases + + + + Joins an enumeration with a value converter and a delimiter to a string + The inner type of the enumeration + The enumeration + An optional value converter (from T to string) + An optional delimiter + The values joined into a string + + + + Converts an array of types (for example methods arguments) into a human readable form + The array of types + A human readable description including brackets + + + + A full description of a type + The type + A human readable description + + + + A a full description of a method or a constructor without assembly details but with generics + The method/constructor + A human readable description + + + + A helper converting parameter infos to types + The array of parameter infos + An array of types + + + + A helper to access a value via key from a dictionary + The key type + The value type + The dictionary + The key + The value for the key or the default value (of T) if that key does not exist + + + + A helper to access a value via key from a dictionary with extra casting + The value type + The dictionary + The key + The value for the key or the default value (of T) if that key does not exist or cannot be cast to T + + + + Escapes Unicode and ASCII non printable characters + The string to convert + The string to convert + A string literal surrounded by + + + + Extensions for + + + + Returns if an is initialized and valid + The + + + + Shortcut for testing whether the operand is equal to a non-null value + The + The value + True if the operand has the same type and is equal to the value + + + + Shortcut for testing whether the operand is equal to a non-null value + The + The value + True if the operand is equal to the value + This is an optimized version of for + + + + Shortcut for code.opcode == opcode && code.OperandIs(operand) + The + The + The operand value + True if the opcode is equal to the given opcode and the operand has the same type and is equal to the given operand + + + + Shortcut for code.opcode == opcode && code.OperandIs(operand) + The + The + The operand value + True if the opcode is equal to the given opcode and the operand is equal to the given operand + This is an optimized version of for + + + + Tests for any form of Ldarg* + The + The (optional) index + True if it matches one of the variations + + + + Tests for Ldarga/Ldarga_S + The + The (optional) index + True if it matches one of the variations + + + + Tests for Starg/Starg_S + The + The (optional) index + True if it matches one of the variations + + + + Tests for any form of Ldloc* + The + The optional local variable + True if it matches one of the variations + + + + Tests for any form of Stloc* + The + The optional local variable + True if it matches one of the variations + + + + Tests if the code instruction branches + The + The label if the instruction is a branch operation or if not + True if the instruction branches + + + + Tests if the code instruction calls the method/constructor + The + The method + True if the instruction calls the method or constructor + + + + Tests if the code instruction loads a constant + The + True if the instruction loads a constant + + + + Tests if the code instruction loads an integer constant + The + The integer constant + True if the instruction loads the constant + + + + Tests if the code instruction loads a floating point constant + The + The floating point constant + True if the instruction loads the constant + + + + Tests if the code instruction loads an enum constant + The + The enum + True if the instruction loads the constant + + + + Tests if the code instruction loads a string constant + The + The string + True if the instruction loads the constant + + + + Tests if the code instruction loads a field + The + The field + Set to true if the address of the field is loaded + True if the instruction loads the field + + + + Tests if the code instruction stores a field + The + The field + True if the instruction stores this field + + + + Adds labels to the code instruction and return it + The + One or several to add + The same code instruction + + + Adds labels to the code instruction and return it + The + An enumeration of + The same code instruction + + + Extracts all labels from the code instruction and returns them + The + A list of + + + Moves all labels from the code instruction to another one + The to move the labels from + The other to move the labels to + The code instruction labels were moved from (now empty) + + + Moves all labels from another code instruction to the current one + The to move the labels to + The other to move the labels from + The code instruction that received the labels + + + Adds ExceptionBlocks to the code instruction and return it + The + One or several to add + The same code instruction + + + Adds ExceptionBlocks to the code instruction and return it + The + An enumeration of + The same code instruction + + + Extracts all ExceptionBlocks from the code instruction and returns them + The + A list of + + + Moves all ExceptionBlocks from the code instruction to another one + The to move the ExceptionBlocks from + The other to move the ExceptionBlocks to + The code instruction blocks were moved from (now empty) + + + Moves all ExceptionBlocks from another code instruction to the current one + The to move the ExceptionBlocks to + The other to move the ExceptionBlocks from + The code instruction that received the blocks + + + General extensions for collections + + + + A simple way to execute code for every element in a collection + The inner type of the collection + The collection + The action to execute + + + + A simple way to execute code for elements in a collection matching a condition + The inner type of the collection + The collection + The predicate + The action to execute + + + + A helper to add an item to a collection + The inner type of the collection + The collection + The item to add + The collection containing the item + + + + A helper to add an item to an array + The inner type of the collection + The array + The item to add + The array containing the item + + + + A helper to add items to an array + The inner type of the collection + The array + The items to add + The array containing the items + + + + General extensions for collections + + + + Tests a class member if it has an IL method body (external methods for example don't have a body) + The member to test + Returns true if the member has an IL body or false if not + + + A file log for debugging + + + + Set this to make Harmony write its log content to this stream + + + + Full pathname of the log file, defaults to a file called harmony.log.txt on your Desktop + + + + The indent character. The default is tab + + + + The current indent level + + + + Changes the indentation level + The value to add to the indentation level + + + + Log a string in a buffered way. Use this method only if you are sure that FlushBuffer will be called + or else logging information is incomplete in case of a crash + The string to log + + + + Logs a list of string in a buffered way. Use this method only if you are sure that FlushBuffer will be called + or else logging information is incomplete in case of a crash + A list of strings to log (they will not be re-indented) + + + + Returns the log buffer and optionally empties it + True to empty the buffer + The buffer. + + + + Replaces the buffer with new lines + The lines to store + + + + Flushes the log buffer to disk (use in combination with LogBuffered) + + + + Log a string directly to disk. Slower method that prevents missing information in case of a crash + The string to log. + + + + Log a string directly to disk if Harmony.DEBUG is true. Slower method that prevents missing information in case of a crash + The string to log. + + + + Resets and deletes the log + + + + Logs some bytes as hex values + The pointer to some memory + The length of bytes to log + + + + A helper class to retrieve reflection info for non-private methods + + + + Given a lambda expression that calls a method, returns the method info + The lambda expression using the method + The method in the lambda expression + + + + Given a lambda expression that calls a method, returns the method info + The generic type + The lambda expression using the method + The method in the lambda expression + + + + Given a lambda expression that calls a method, returns the method info + The generic type + The generic result type + The lambda expression using the method + The method in the lambda expression + + + + Given a lambda expression that calls a method, returns the method info + The lambda expression using the method + The method in the lambda expression + + + + A reflection helper to read and write private elements + The result type defined by GetValue() + + + + Creates a traverse instance from an existing instance + The existing instance + + + + Gets/Sets the current value + The value to read or write + + + + A reflection helper to read and write private elements + + + + Creates a new traverse instance from a class/type + The class/type + A instance + + + + Creates a new traverse instance from a class T + The class + A instance + + + + Creates a new traverse instance from an instance + The object + A instance + + + + Creates a new traverse instance from a named type + The type name, for format see + A instance + + + + Creates a new and empty traverse instance + + + + Creates a new traverse instance from a class/type + The class/type + + + + Creates a new traverse instance from an instance + The object + + + + Gets the current value + The value + + + + Gets the current value + The type of the value + The value + + + + Invokes the current method with arguments and returns the result + The method arguments + The value returned by the method + + + + Invokes the current method with arguments and returns the result + The type of the value + The method arguments + The value returned by the method + + + + Sets a value of the current field or property + The value + The same traverse instance + + + + Gets the type of the current field or property + The type + + + + Moves the current traverse instance to a inner type + The type name + A traverse instance + + + + Moves the current traverse instance to a field + The type name + A traverse instance + + + + Moves the current traverse instance to a field + The type of the field + The type name + A traverse instance + + + + Gets all fields of the current type + A list of field names + + + + Moves the current traverse instance to a property + The type name + Optional property index + A traverse instance + + + + Moves the current traverse instance to a field + The type of the property + The type name + Optional property index + A traverse instance + + + + Gets all properties of the current type + A list of property names + + + + Moves the current traverse instance to a method + The name of the method + The arguments defining the argument types of the method overload + A traverse instance + + + + Moves the current traverse instance to a method + The name of the method + The argument types of the method + The arguments for the method + A traverse instance + + + + Gets all methods of the current type + A list of method names + + + + Checks if the current traverse instance is for a field + True if its a field + + + + Checks if the current traverse instance is for a property + True if its a property + + + + Checks if the current traverse instance is for a method + True if its a method + + + + Checks if the current traverse instance is for a type + True if its a type + + + + Iterates over all fields of the current type and executes a traverse action + Original object + The action receiving a instance for each field + + + + Iterates over all fields of the current type and executes a traverse action + Original object + Target object + The action receiving a pair of instances for each field pair + + + + Iterates over all fields of the current type and executes a traverse action + Original object + Target object + The action receiving a dot path representing the field pair and the instances + + + + Iterates over all properties of the current type and executes a traverse action + Original object + The action receiving a instance for each property + + + + Iterates over all properties of the current type and executes a traverse action + Original object + Target object + The action receiving a pair of instances for each property pair + + + + Iterates over all properties of the current type and executes a traverse action + Original object + Target object + The action receiving a dot path representing the property pair and the instances + + + + A default field action that copies fields to fields + + + + Returns a string that represents the current traverse + A string representation + + + + diff --git a/Assemblies/0MultiplayerAPI.dll b/Assemblies/0MultiplayerAPI.dll new file mode 100644 index 0000000..38b8a0e Binary files /dev/null and b/Assemblies/0MultiplayerAPI.dll differ diff --git a/Assemblies/ResearchTree.dll b/Assemblies/ResearchTree.dll index 5823019..c6237a9 100644 Binary files a/Assemblies/ResearchTree.dll and b/Assemblies/ResearchTree.dll differ diff --git a/Assemblies/System.ValueTuple.dll b/Assemblies/System.ValueTuple.dll new file mode 100644 index 0000000..b63769a Binary files /dev/null and b/Assemblies/System.ValueTuple.dll differ diff --git a/Assemblies/System.ValueTuple.xml b/Assemblies/System.ValueTuple.xml new file mode 100644 index 0000000..92b0c5c --- /dev/null +++ b/Assemblies/System.ValueTuple.xml @@ -0,0 +1,1299 @@ + + + + System.ValueTuple + + + + + Indicates that the use of on a member is meant to be treated as a tuple with element names. + + + + + Initializes a new instance of the class. + + + Specifies, in a pre-order depth-first traversal of a type's + construction, which occurrences are + meant to carry element names. + + + This constructor is meant to be used on types that contain an + instantiation of that contains + element names. For instance, if C is a generic type with + two type parameters, then a use of the constructed type C{, might be intended to + treat the first type argument as a tuple with element names and the + second as a tuple without element names. In which case, the + appropriate attribute specification should use a + transformNames value of { "name1", "name2", null, null, + null }. + + + + + Specifies, in a pre-order depth-first traversal of a type's + construction, which elements are + meant to carry element names. + + + + + Provides extension methods for instances to interop with C# tuples features (deconstruction syntax, converting from and to ). + + + + + Deconstruct a properly nested with 1 elements. + + + + + Deconstruct a properly nested with 2 elements. + + + + + Deconstruct a properly nested with 3 elements. + + + + + Deconstruct a properly nested with 4 elements. + + + + + Deconstruct a properly nested with 5 elements. + + + + + Deconstruct a properly nested with 6 elements. + + + + + Deconstruct a properly nested with 7 elements. + + + + + Deconstruct a properly nested with 8 elements. + + + + + Deconstruct a properly nested with 9 elements. + + + + + Deconstruct a properly nested with 10 elements. + + + + + Deconstruct a properly nested with 11 elements. + + + + + Deconstruct a properly nested with 12 elements. + + + + + Deconstruct a properly nested with 13 elements. + + + + + Deconstruct a properly nested with 14 elements. + + + + + Deconstruct a properly nested with 15 elements. + + + + + Deconstruct a properly nested with 16 elements. + + + + + Deconstruct a properly nested with 17 elements. + + + + + Deconstruct a properly nested with 18 elements. + + + + + Deconstruct a properly nested with 19 elements. + + + + + Deconstruct a properly nested with 20 elements. + + + + + Deconstruct a properly nested with 21 elements. + + + + + Make a properly nested from a properly nested with 1 element. + + + + + Make a properly nested from a properly nested with 2 elements. + + + + + Make a properly nested from a properly nested with 3 elements. + + + + + Make a properly nested from a properly nested with 4 elements. + + + + + Make a properly nested from a properly nested with 5 elements. + + + + + Make a properly nested from a properly nested with 6 elements. + + + + + Make a properly nested from a properly nested with 7 elements. + + + + + Make a properly nested from a properly nested with 8 elements. + + + + + Make a properly nested from a properly nested with 9 elements. + + + + + Make a properly nested from a properly nested with 10 elements. + + + + + Make a properly nested from a properly nested with 11 elements. + + + + + Make a properly nested from a properly nested with 12 elements. + + + + + Make a properly nested from a properly nested with 13 elements. + + + + + Make a properly nested from a properly nested with 14 elements. + + + + + Make a properly nested from a properly nested with 15 elements. + + + + + Make a properly nested from a properly nested with 16 elements. + + + + + Make a properly nested from a properly nested with 17 elements. + + + + + Make a properly nested from a properly nested with 18 elements. + + + + + Make a properly nested from a properly nested with 19 elements. + + + + + Make a properly nested from a properly nested with 20 elements. + + + + + Make a properly nested from a properly nested with 21 elements. + + + + + Make a properly nested from a properly nested with 1 element. + + + + + Make a properly nested from a properly nested with 2 elements. + + + + + Make a properly nested from a properly nested with 3 elements. + + + + + Make a properly nested from a properly nested with 4 elements. + + + + + Make a properly nested from a properly nested with 5 elements. + + + + + Make a properly nested from a properly nested with 6 elements. + + + + + Make a properly nested from a properly nested with 7 elements. + + + + + Make a properly nested from a properly nested with 8 elements. + + + + + Make a properly nested from a properly nested with 9 elements. + + + + + Make a properly nested from a properly nested with 10 elements. + + + + + Make a properly nested from a properly nested with 11 elements. + + + + + Make a properly nested from a properly nested with 12 elements. + + + + + Make a properly nested from a properly nested with 13 elements. + + + + + Make a properly nested from a properly nested with 14 elements. + + + + + Make a properly nested from a properly nested with 15 elements. + + + + + Make a properly nested from a properly nested with 16 elements. + + + + + Make a properly nested from a properly nested with 17 elements. + + + + + Make a properly nested from a properly nested with 18 elements. + + + + + Make a properly nested from a properly nested with 19 elements. + + + + + Make a properly nested from a properly nested with 20 elements. + + + + + Make a properly nested from a properly nested with 21 elements. + + + + + Helper so we can call some tuple methods recursively without knowing the underlying types. + + + + + The ValueTuple types (from arity 0 to 8) comprise the runtime implementation that underlies tuples in C# and struct tuples in F#. + Aside from created via language syntax, they are most easily created via the ValueTuple.Create factory methods. + The System.ValueTuple types differ from the System.Tuple types in that: + - they are structs rather than classes, + - they are mutable rather than readonly, and + - their members (such as Item1, Item2, etc) are fields rather than properties. + + + + + Returns a value that indicates whether the current instance is equal to a specified object. + + The object to compare with this instance. + if is a . + + + Returns a value indicating whether this instance is equal to a specified value. + An instance to compare to this instance. + true if has the same value as this instance; otherwise, false. + + + Compares this instance to a specified instance and returns an indication of their relative values. + An instance to compare. + + A signed number indicating the relative values of this instance and . + Returns less than zero if this instance is less than , zero if this + instance is equal to , and greater than zero if this instance is greater + than . + + + + Returns the hash code for this instance. + A 32-bit signed integer hash code. + + + + Returns a string that represents the value of this instance. + + The string representation of this instance. + + The string returned by this method takes the form (). + + + + Creates a new struct 0-tuple. + A 0-tuple. + + + Creates a new struct 1-tuple, or singleton. + The type of the first component of the tuple. + The value of the first component of the tuple. + A 1-tuple (singleton) whose value is (item1). + + + Creates a new struct 2-tuple, or pair. + The type of the first component of the tuple. + The type of the second component of the tuple. + The value of the first component of the tuple. + The value of the second component of the tuple. + A 2-tuple (pair) whose value is (item1, item2). + + + Creates a new struct 3-tuple, or triple. + The type of the first component of the tuple. + The type of the second component of the tuple. + The type of the third component of the tuple. + The value of the first component of the tuple. + The value of the second component of the tuple. + The value of the third component of the tuple. + A 3-tuple (triple) whose value is (item1, item2, item3). + + + Creates a new struct 4-tuple, or quadruple. + The type of the first component of the tuple. + The type of the second component of the tuple. + The type of the third component of the tuple. + The type of the fourth component of the tuple. + The value of the first component of the tuple. + The value of the second component of the tuple. + The value of the third component of the tuple. + The value of the fourth component of the tuple. + A 4-tuple (quadruple) whose value is (item1, item2, item3, item4). + + + Creates a new struct 5-tuple, or quintuple. + The type of the first component of the tuple. + The type of the second component of the tuple. + The type of the third component of the tuple. + The type of the fourth component of the tuple. + The type of the fifth component of the tuple. + The value of the first component of the tuple. + The value of the second component of the tuple. + The value of the third component of the tuple. + The value of the fourth component of the tuple. + The value of the fifth component of the tuple. + A 5-tuple (quintuple) whose value is (item1, item2, item3, item4, item5). + + + Creates a new struct 6-tuple, or sextuple. + The type of the first component of the tuple. + The type of the second component of the tuple. + The type of the third component of the tuple. + The type of the fourth component of the tuple. + The type of the fifth component of the tuple. + The type of the sixth component of the tuple. + The value of the first component of the tuple. + The value of the second component of the tuple. + The value of the third component of the tuple. + The value of the fourth component of the tuple. + The value of the fifth component of the tuple. + The value of the sixth component of the tuple. + A 6-tuple (sextuple) whose value is (item1, item2, item3, item4, item5, item6). + + + Creates a new struct 7-tuple, or septuple. + The type of the first component of the tuple. + The type of the second component of the tuple. + The type of the third component of the tuple. + The type of the fourth component of the tuple. + The type of the fifth component of the tuple. + The type of the sixth component of the tuple. + The type of the seventh component of the tuple. + The value of the first component of the tuple. + The value of the second component of the tuple. + The value of the third component of the tuple. + The value of the fourth component of the tuple. + The value of the fifth component of the tuple. + The value of the sixth component of the tuple. + The value of the seventh component of the tuple. + A 7-tuple (septuple) whose value is (item1, item2, item3, item4, item5, item6, item7). + + + Creates a new struct 8-tuple, or octuple. + The type of the first component of the tuple. + The type of the second component of the tuple. + The type of the third component of the tuple. + The type of the fourth component of the tuple. + The type of the fifth component of the tuple. + The type of the sixth component of the tuple. + The type of the seventh component of the tuple. + The type of the eighth component of the tuple. + The value of the first component of the tuple. + The value of the second component of the tuple. + The value of the third component of the tuple. + The value of the fourth component of the tuple. + The value of the fifth component of the tuple. + The value of the sixth component of the tuple. + The value of the seventh component of the tuple. + The value of the eighth component of the tuple. + An 8-tuple (octuple) whose value is (item1, item2, item3, item4, item5, item6, item7, item8). + + + Represents a 1-tuple, or singleton, as a value type. + The type of the tuple's only component. + + + + The current instance's first component. + + + + + Initializes a new instance of the value type. + + The value of the tuple's first component. + + + + Returns a value that indicates whether the current instance is equal to a specified object. + + The object to compare with this instance. + if the current instance is equal to the specified object; otherwise, . + + The parameter is considered to be equal to the current instance under the following conditions: + + It is a value type. + Its components are of the same types as those of the current instance. + Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + + + + + + Returns a value that indicates whether the current + instance is equal to a specified . + + The tuple to compare with this instance. + if the current instance is equal to the specified tuple; otherwise, . + + The parameter is considered to be equal to the current instance if each of its field + is equal to that of the current instance, using the default comparer for that field's type. + + + + Compares this instance to a specified instance and returns an indication of their relative values. + An instance to compare. + + A signed number indicating the relative values of this instance and . + Returns less than zero if this instance is less than , zero if this + instance is equal to , and greater than zero if this instance is greater + than . + + + + + Returns the hash code for the current instance. + + A 32-bit signed integer hash code. + + + + Returns a string that represents the value of this instance. + + The string representation of this instance. + + The string returned by this method takes the form (Item1), + where Item1 represents the value of . If the field is , + it is represented as . + + + + + Represents a 2-tuple, or pair, as a value type. + + The type of the tuple's first component. + The type of the tuple's second component. + + + + The current instance's first component. + + + + + The current instance's second component. + + + + + Initializes a new instance of the value type. + + The value of the tuple's first component. + The value of the tuple's second component. + + + + Returns a value that indicates whether the current instance is equal to a specified object. + + The object to compare with this instance. + if the current instance is equal to the specified object; otherwise, . + + + The parameter is considered to be equal to the current instance under the following conditions: + + It is a value type. + Its components are of the same types as those of the current instance. + Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + + + + + + Returns a value that indicates whether the current instance is equal to a specified . + + The tuple to compare with this instance. + if the current instance is equal to the specified tuple; otherwise, . + + The parameter is considered to be equal to the current instance if each of its fields + are equal to that of the current instance, using the default comparer for that field's type. + + + + + Returns a value that indicates whether the current instance is equal to a specified object based on a specified comparison method. + + The object to compare with this instance. + An object that defines the method to use to evaluate whether the two objects are equal. + if the current instance is equal to the specified object; otherwise, . + + + This member is an explicit interface member implementation. It can be used only when the + instance is cast to an interface. + + The implementation is called only if other is not , + and if it can be successfully cast (in C#) or converted (in Visual Basic) to a + whose components are of the same types as those of the current instance. The IStructuralEquatable.Equals(Object, IEqualityComparer) method + first passes the values of the objects to be compared to the + implementation. If this method call returns , the method is + called again and passed the values of the two instances. + + + + Compares this instance to a specified instance and returns an indication of their relative values. + An instance to compare. + + A signed number indicating the relative values of this instance and . + Returns less than zero if this instance is less than , zero if this + instance is equal to , and greater than zero if this instance is greater + than . + + + + + Returns the hash code for the current instance. + + A 32-bit signed integer hash code. + + + + Returns a string that represents the value of this instance. + + The string representation of this instance. + + The string returned by this method takes the form (Item1, Item2), + where Item1 and Item2 represent the values of the + and fields. If either field value is , + it is represented as . + + + + + Represents a 3-tuple, or triple, as a value type. + + The type of the tuple's first component. + The type of the tuple's second component. + The type of the tuple's third component. + + + + The current instance's first component. + + + + + The current instance's second component. + + + + + The current instance's third component. + + + + + Initializes a new instance of the value type. + + The value of the tuple's first component. + The value of the tuple's second component. + The value of the tuple's third component. + + + + Returns a value that indicates whether the current instance is equal to a specified object. + + The object to compare with this instance. + if the current instance is equal to the specified object; otherwise, . + + The parameter is considered to be equal to the current instance under the following conditions: + + It is a value type. + Its components are of the same types as those of the current instance. + Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + + + + + + Returns a value that indicates whether the current + instance is equal to a specified . + + The tuple to compare with this instance. + if the current instance is equal to the specified tuple; otherwise, . + + The parameter is considered to be equal to the current instance if each of its fields + are equal to that of the current instance, using the default comparer for that field's type. + + + + Compares this instance to a specified instance and returns an indication of their relative values. + An instance to compare. + + A signed number indicating the relative values of this instance and . + Returns less than zero if this instance is less than , zero if this + instance is equal to , and greater than zero if this instance is greater + than . + + + + + Returns the hash code for the current instance. + + A 32-bit signed integer hash code. + + + + Returns a string that represents the value of this instance. + + The string representation of this instance. + + The string returned by this method takes the form (Item1, Item2, Item3). + If any field value is , it is represented as . + + + + + Represents a 4-tuple, or quadruple, as a value type. + + The type of the tuple's first component. + The type of the tuple's second component. + The type of the tuple's third component. + The type of the tuple's fourth component. + + + + The current instance's first component. + + + + + The current instance's second component. + + + + + The current instance's third component. + + + + + The current instance's fourth component. + + + + + Initializes a new instance of the value type. + + The value of the tuple's first component. + The value of the tuple's second component. + The value of the tuple's third component. + The value of the tuple's fourth component. + + + + Returns a value that indicates whether the current instance is equal to a specified object. + + The object to compare with this instance. + if the current instance is equal to the specified object; otherwise, . + + The parameter is considered to be equal to the current instance under the following conditions: + + It is a value type. + Its components are of the same types as those of the current instance. + Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + + + + + + Returns a value that indicates whether the current + instance is equal to a specified . + + The tuple to compare with this instance. + if the current instance is equal to the specified tuple; otherwise, . + + The parameter is considered to be equal to the current instance if each of its fields + are equal to that of the current instance, using the default comparer for that field's type. + + + + Compares this instance to a specified instance and returns an indication of their relative values. + An instance to compare. + + A signed number indicating the relative values of this instance and . + Returns less than zero if this instance is less than , zero if this + instance is equal to , and greater than zero if this instance is greater + than . + + + + + Returns the hash code for the current instance. + + A 32-bit signed integer hash code. + + + + Returns a string that represents the value of this instance. + + The string representation of this instance. + + The string returned by this method takes the form (Item1, Item2, Item3, Item4). + If any field value is , it is represented as . + + + + + Represents a 5-tuple, or quintuple, as a value type. + + The type of the tuple's first component. + The type of the tuple's second component. + The type of the tuple's third component. + The type of the tuple's fourth component. + The type of the tuple's fifth component. + + + + The current instance's first component. + + + + + The current instance's second component. + + + + + The current instance's third component. + + + + + The current instance's fourth component. + + + + + The current instance's fifth component. + + + + + Initializes a new instance of the value type. + + The value of the tuple's first component. + The value of the tuple's second component. + The value of the tuple's third component. + The value of the tuple's fourth component. + The value of the tuple's fifth component. + + + + Returns a value that indicates whether the current instance is equal to a specified object. + + The object to compare with this instance. + if the current instance is equal to the specified object; otherwise, . + + The parameter is considered to be equal to the current instance under the following conditions: + + It is a value type. + Its components are of the same types as those of the current instance. + Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + + + + + + Returns a value that indicates whether the current + instance is equal to a specified . + + The tuple to compare with this instance. + if the current instance is equal to the specified tuple; otherwise, . + + The parameter is considered to be equal to the current instance if each of its fields + are equal to that of the current instance, using the default comparer for that field's type. + + + + Compares this instance to a specified instance and returns an indication of their relative values. + An instance to compare. + + A signed number indicating the relative values of this instance and . + Returns less than zero if this instance is less than , zero if this + instance is equal to , and greater than zero if this instance is greater + than . + + + + + Returns the hash code for the current instance. + + A 32-bit signed integer hash code. + + + + Returns a string that represents the value of this instance. + + The string representation of this instance. + + The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5). + If any field value is , it is represented as . + + + + + Represents a 6-tuple, or sixtuple, as a value type. + + The type of the tuple's first component. + The type of the tuple's second component. + The type of the tuple's third component. + The type of the tuple's fourth component. + The type of the tuple's fifth component. + The type of the tuple's sixth component. + + + + The current instance's first component. + + + + + The current instance's second component. + + + + + The current instance's third component. + + + + + The current instance's fourth component. + + + + + The current instance's fifth component. + + + + + The current instance's sixth component. + + + + + Initializes a new instance of the value type. + + The value of the tuple's first component. + The value of the tuple's second component. + The value of the tuple's third component. + The value of the tuple's fourth component. + The value of the tuple's fifth component. + The value of the tuple's sixth component. + + + + Returns a value that indicates whether the current instance is equal to a specified object. + + The object to compare with this instance. + if the current instance is equal to the specified object; otherwise, . + + The parameter is considered to be equal to the current instance under the following conditions: + + It is a value type. + Its components are of the same types as those of the current instance. + Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + + + + + + Returns a value that indicates whether the current + instance is equal to a specified . + + The tuple to compare with this instance. + if the current instance is equal to the specified tuple; otherwise, . + + The parameter is considered to be equal to the current instance if each of its fields + are equal to that of the current instance, using the default comparer for that field's type. + + + + Compares this instance to a specified instance and returns an indication of their relative values. + An instance to compare. + + A signed number indicating the relative values of this instance and . + Returns less than zero if this instance is less than , zero if this + instance is equal to , and greater than zero if this instance is greater + than . + + + + + Returns the hash code for the current instance. + + A 32-bit signed integer hash code. + + + + Returns a string that represents the value of this instance. + + The string representation of this instance. + + The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6). + If any field value is , it is represented as . + + + + + Represents a 7-tuple, or sentuple, as a value type. + + The type of the tuple's first component. + The type of the tuple's second component. + The type of the tuple's third component. + The type of the tuple's fourth component. + The type of the tuple's fifth component. + The type of the tuple's sixth component. + The type of the tuple's seventh component. + + + + The current instance's first component. + + + + + The current instance's second component. + + + + + The current instance's third component. + + + + + The current instance's fourth component. + + + + + The current instance's fifth component. + + + + + The current instance's sixth component. + + + + + The current instance's seventh component. + + + + + Initializes a new instance of the value type. + + The value of the tuple's first component. + The value of the tuple's second component. + The value of the tuple's third component. + The value of the tuple's fourth component. + The value of the tuple's fifth component. + The value of the tuple's sixth component. + The value of the tuple's seventh component. + + + + Returns a value that indicates whether the current instance is equal to a specified object. + + The object to compare with this instance. + if the current instance is equal to the specified object; otherwise, . + + The parameter is considered to be equal to the current instance under the following conditions: + + It is a value type. + Its components are of the same types as those of the current instance. + Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + + + + + + Returns a value that indicates whether the current + instance is equal to a specified . + + The tuple to compare with this instance. + if the current instance is equal to the specified tuple; otherwise, . + + The parameter is considered to be equal to the current instance if each of its fields + are equal to that of the current instance, using the default comparer for that field's type. + + + + Compares this instance to a specified instance and returns an indication of their relative values. + An instance to compare. + + A signed number indicating the relative values of this instance and . + Returns less than zero if this instance is less than , zero if this + instance is equal to , and greater than zero if this instance is greater + than . + + + + + Returns the hash code for the current instance. + + A 32-bit signed integer hash code. + + + + Returns a string that represents the value of this instance. + + The string representation of this instance. + + The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6, Item7). + If any field value is , it is represented as . + + + + + Represents an 8-tuple, or octuple, as a value type. + + The type of the tuple's first component. + The type of the tuple's second component. + The type of the tuple's third component. + The type of the tuple's fourth component. + The type of the tuple's fifth component. + The type of the tuple's sixth component. + The type of the tuple's seventh component. + The type of the tuple's eighth component. + + + + The current instance's first component. + + + + + The current instance's second component. + + + + + The current instance's third component. + + + + + The current instance's fourth component. + + + + + The current instance's fifth component. + + + + + The current instance's sixth component. + + + + + The current instance's seventh component. + + + + + The current instance's eighth component. + + + + + Initializes a new instance of the value type. + + The value of the tuple's first component. + The value of the tuple's second component. + The value of the tuple's third component. + The value of the tuple's fourth component. + The value of the tuple's fifth component. + The value of the tuple's sixth component. + The value of the tuple's seventh component. + The value of the tuple's eight component. + + + + Returns a value that indicates whether the current instance is equal to a specified object. + + The object to compare with this instance. + if the current instance is equal to the specified object; otherwise, . + + The parameter is considered to be equal to the current instance under the following conditions: + + It is a value type. + Its components are of the same types as those of the current instance. + Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + + + + + + Returns a value that indicates whether the current + instance is equal to a specified . + + The tuple to compare with this instance. + if the current instance is equal to the specified tuple; otherwise, . + + The parameter is considered to be equal to the current instance if each of its fields + are equal to that of the current instance, using the default comparer for that field's type. + + + + Compares this instance to a specified instance and returns an indication of their relative values. + An instance to compare. + + A signed number indicating the relative values of this instance and . + Returns less than zero if this instance is less than , zero if this + instance is equal to , and greater than zero if this instance is greater + than . + + + + + Returns the hash code for the current instance. + + A 32-bit signed integer hash code. + + + + Returns a string that represents the value of this instance. + + The string representation of this instance. + + The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6, Item7, Rest). + If any field value is , it is represented as . + + + + diff --git a/Source/Assets.cs b/Source/Assets.cs index 3eb714f..9f2574a 100644 --- a/Source/Assets.cs +++ b/Source/Assets.cs @@ -1,8 +1,8 @@ // Assets.cs // Copyright Karel Kroeze, 2018-2020 -using System.Collections.Generic; using RimWorld; +using System.Collections.Generic; using UnityEngine; using Verse; @@ -11,44 +11,44 @@ namespace FluffyResearchTree [StaticConstructorOnStartup] public static class Assets { - public static Texture2D Button = ContentFinder.Get( "Buttons/button" ); - public static Texture2D ButtonActive = ContentFinder.Get( "Buttons/button-active" ); - public static Texture2D ResearchIcon = ContentFinder.Get( "Icons/Research" ); - public static Texture2D MoreIcon = ContentFinder.Get( "Icons/more" ); - public static Texture2D Lock = ContentFinder.Get( "Icons/padlock" ); - internal static readonly Texture2D CircleFill = ContentFinder.Get( "Icons/circle-fill" ); + public static Texture2D Button = ContentFinder.Get("Buttons/button"); + public static Texture2D ButtonActive = ContentFinder.Get("Buttons/button-active"); + public static Texture2D ResearchIcon = ContentFinder.Get("Icons/Research"); + public static Texture2D MoreIcon = ContentFinder.Get("Icons/more"); + public static Texture2D Lock = ContentFinder.Get("Icons/padlock"); + internal static readonly Texture2D CircleFill = ContentFinder.Get("Icons/circle-fill"); - public static Color NegativeMouseoverColor = new Color( .4f, .1f, .1f ); - public static Dictionary ColorCompleted = new Dictionary(); - public static Dictionary ColorAvailable = new Dictionary(); - public static Dictionary ColorUnavailable = new Dictionary(); - public static Color TechLevelColor = new Color( 1f, 1f, 1f, .2f ); + public static Color NegativeMouseoverColor = new Color(.4f, .1f, .1f); + public static Dictionary ColorCompleted = new Dictionary(); + public static Dictionary ColorAvailable = new Dictionary(); + public static Dictionary ColorUnavailable = new Dictionary(); + public static Color TechLevelColor = new Color(1f, 1f, 1f, .2f); public static Texture2D SlightlyDarkBackground = - SolidColorMaterials.NewSolidColorTexture( 0f, 0f, 0f, .1f ); + SolidColorMaterials.NewSolidColorTexture(0f, 0f, 0f, .1f); public static Texture2D Search = - ContentFinder.Get( "Icons/magnifying-glass" ); + ContentFinder.Get("Icons/magnifying-glass"); static Assets() { var techlevels = Tree.RelevantTechLevels; - var n = techlevels.Count; - for ( var i = 0; i < n; i++ ) + var n = techlevels.Count; + for (var i = 0; i < n; i++) { - ColorCompleted[techlevels[i]] = Color.HSVToRGB( 1f / n * i, .75f, .75f ); - ColorAvailable[techlevels[i]] = Color.HSVToRGB( 1f / n * i, .33f, .33f ); - ColorUnavailable[techlevels[i]] = Color.HSVToRGB( 1f / n * i, .125f, .33f ); + ColorCompleted[techlevels[i]] = Color.HSVToRGB(1f / n * i, .75f, .75f); + ColorAvailable[techlevels[i]] = Color.HSVToRGB(1f / n * i, .33f, .33f); + ColorUnavailable[techlevels[i]] = Color.HSVToRGB(1f / n * i, .125f, .33f); } } [StaticConstructorOnStartup] public static class Lines { - public static Texture2D Circle = ContentFinder.Get( "Lines/Outline/circle" ); - public static Texture2D End = ContentFinder.Get( "Lines/Outline/end" ); - public static Texture2D EW = ContentFinder.Get( "Lines/Outline/ew" ); - public static Texture2D NS = ContentFinder.Get( "Lines/Outline/ns" ); + public static Texture2D Circle = ContentFinder.Get("Lines/Outline/circle"); + public static Texture2D End = ContentFinder.Get("Lines/Outline/end"); + public static Texture2D EW = ContentFinder.Get("Lines/Outline/ew"); + public static Texture2D NS = ContentFinder.Get("Lines/Outline/ns"); } } } \ No newline at end of file diff --git a/Source/Constants.cs b/Source/Constants.cs index 77ec3a5..4dba32e 100644 --- a/Source/Constants.cs +++ b/Source/Constants.cs @@ -7,18 +7,18 @@ namespace FluffyResearchTree { public static class Constants { - public const double Epsilon = 1e-4; - public const float HubSize = 16f; - public const float DetailedModeZoomLevelCutoff = 1.5f; - public const float Margin = 6f; - public const float QueueLabelSize = 30f; - public const float SmallQueueLabelSize = 20f; - public const float AbsoluteMaxZoomLevel = 3f; - public const float ZoomStep = .05f; - public static readonly Vector2 IconSize = new Vector2( 18f, 18f ); - public static readonly Vector2 NodeMargins = new Vector2( 50f, 10f ); - public static readonly Vector2 NodeSize = new Vector2( 200f, 50f ); - public static readonly float TopBarHeight = NodeSize.y + Margin * 2; - public static readonly Vector2 TechLevelLabelSize = new Vector2( 200f, 30f ); + public const double Epsilon = 1e-4; + public const float HubSize = 16f; + public const float DetailedModeZoomLevelCutoff = 1.5f; + public const float Margin = 6f; + public const float QueueLabelSize = 30f; + public const float SmallQueueLabelSize = 20f; + public const float AbsoluteMaxZoomLevel = 3f; + public const float ZoomStep = .05f; + public static readonly Vector2 IconSize = new Vector2(18f, 18f); + public static readonly Vector2 NodeMargins = new Vector2(50f, 10f); + public static readonly Vector2 NodeSize = new Vector2(200f, 50f); + public static readonly float TopBarHeight = NodeSize.y + Margin * 2; + public static readonly Vector2 TechLevelLabelSize = new Vector2(200f, 30f); } } \ No newline at end of file diff --git a/Source/Extensions/Building_ResearchBench_Extensions.cs b/Source/Extensions/Building_ResearchBench_Extensions.cs index e10aa41..fe33c6a 100644 --- a/Source/Extensions/Building_ResearchBench_Extensions.cs +++ b/Source/Extensions/Building_ResearchBench_Extensions.cs @@ -1,21 +1,21 @@ // Building_ResearchBench_Extensions.cs // Copyright Karel Kroeze, 2016-2020 -using System.Linq; using RimWorld; +using System.Linq; using Verse; namespace FluffyResearchTree { public static class Building_ResearchBench_Extensions { - public static bool HasFacility( this Building_ResearchBench building, ThingDef facility ) + public static bool HasFacility(this Building_ResearchBench building, ThingDef facility) { var comp = building.GetComp(); - if ( comp == null ) + if (comp == null) return false; - if ( comp.LinkedFacilitiesListForReading.Select( f => f.def ).Contains( facility ) ) + if (comp.LinkedFacilitiesListForReading.Select(f => f.def).Contains(facility)) return true; return false; diff --git a/Source/Extensions/Def_Extensions.cs b/Source/Extensions/Def_Extensions.cs index 6e2af12..249bfa1 100644 --- a/Source/Extensions/Def_Extensions.cs +++ b/Source/Extensions/Def_Extensions.cs @@ -1,9 +1,9 @@ // Def_Extensions.cs // Copyright Karel Kroeze, 2018-2020 +using RimWorld; using System.Collections.Generic; using System.Linq; -using RimWorld; using UnityEngine; using Verse; @@ -18,10 +18,10 @@ public static class Def_Extensions private static readonly Dictionary _cachedIconColors = new Dictionary(); - public static void DrawColouredIcon( this Def def, Rect canvas ) + public static void DrawColouredIcon(this Def def, Rect canvas) { GUI.color = def.IconColor(); - GUI.DrawTexture( canvas, def.IconTexture(), ScaleMode.ScaleToFit ); + GUI.DrawTexture(canvas, def.IconTexture(), ScaleMode.ScaleToFit); GUI.color = Color.white; } @@ -31,57 +31,54 @@ public static void DrawColouredIcon( this Def def, Rect canvas ) /// /// /// - public static Color IconColor( this Def def ) + public static Color IconColor(this Def def) { // garbage in, garbage out - if ( def == null ) + if (def == null) return Color.cyan; // check cache - if ( _cachedIconColors.ContainsKey( def ) ) return _cachedIconColors[def]; + if (_cachedIconColors.ContainsKey(def)) return _cachedIconColors[def]; // otherwise try to determine icon - var bdef = def as BuildableDef; var tdef = def as ThingDef; - var pdef = def as PawnKindDef; - var rdef = def as RecipeDef; // get product color for recipes - if ( rdef != null ) - if ( !rdef.products.NullOrEmpty() ) + if (def is RecipeDef rdef) + if (!rdef.products.NullOrEmpty()) { - _cachedIconColors.Add( def, rdef.products.First().thingDef.IconColor() ); + _cachedIconColors.Add(def, rdef.products.First().thingDef.IconColor()); return _cachedIconColors[def]; } // get color from final lifestage for pawns - if ( pdef != null ) + if (def is PawnKindDef pdef) { - _cachedIconColors.Add( def, pdef.lifeStages.Last().bodyGraphicData.color ); + _cachedIconColors.Add(def, pdef.lifeStages.Last().bodyGraphicData.color); return _cachedIconColors[def]; } - if ( bdef == null ) + if (!(def is BuildableDef bdef)) { // if we reach this point, def.IconTexture() would return null. Just store and return white to make sure we don't get weird errors down the line. - _cachedIconColors.Add( def, Color.white ); + _cachedIconColors.Add(def, Color.white); return _cachedIconColors[def]; } // built def != listed def if ( - tdef != null && + tdef != null && tdef.entityDefToBuild != null ) { - _cachedIconColors.Add( def, tdef.entityDefToBuild.IconColor() ); + _cachedIconColors.Add(def, tdef.entityDefToBuild.IconColor()); return _cachedIconColors[def]; } // graphic.color set? - if ( bdef.graphic != null ) + if (bdef.graphic != null) { - _cachedIconColors.Add( def, bdef.graphic.color ); + _cachedIconColors.Add(def, bdef.graphic.color); return _cachedIconColors[def]; } @@ -91,13 +88,13 @@ public static Color IconColor( this Def def ) tdef.MadeFromStuff ) { - var stuff = GenStuff.DefaultStuffFor( tdef ); - _cachedIconColors.Add( def, stuff.stuffProps.color ); + var stuff = GenStuff.DefaultStuffFor(tdef); + _cachedIconColors.Add(def, stuff.stuffProps.color); return _cachedIconColors[def]; } // all else failed. - _cachedIconColors.Add( def, Color.white ); + _cachedIconColors.Add(def, Color.white); return _cachedIconColors[def]; } @@ -106,38 +103,35 @@ public static Color IconColor( this Def def ) /// /// /// - public static Texture2D IconTexture( this Def def ) + public static Texture2D IconTexture(this Def def) { // garbage in, garbage out - if ( def == null ) + if (def == null) return null; // check cache - if ( _cachedDefIcons.ContainsKey( def ) ) + if (_cachedDefIcons.ContainsKey(def)) return _cachedDefIcons[def]; // otherwise try to determine icon - var buildableDef = def as BuildableDef; - var thingDef = def as ThingDef; - var pawnKindDef = def as PawnKindDef; - var recipeDef = def as RecipeDef; + var thingDef = def as ThingDef; // recipes will be passed icon of first product, if defined. if ( - recipeDef != null && + def is RecipeDef recipeDef && !recipeDef.products.NullOrEmpty() ) { - _cachedDefIcons.Add( def, recipeDef.products.First().thingDef.IconTexture() ); + _cachedDefIcons.Add(def, recipeDef.products.First().thingDef.IconTexture()); return _cachedDefIcons[def]; } // animals need special treatment ( this will still only work for animals, pawns are a whole different can o' worms ). - if ( pawnKindDef != null ) + if (def is PawnKindDef pawnKindDef) try { _cachedDefIcons.Add( - def, pawnKindDef.lifeStages.Last().bodyGraphicData.Graphic.MatSouth.mainTexture as Texture2D ); + def, pawnKindDef.lifeStages.Last().bodyGraphicData.Graphic.MatSouth.mainTexture as Texture2D); return _cachedDefIcons[def]; } catch @@ -145,21 +139,21 @@ public static Texture2D IconTexture( this Def def ) // ignored } - if ( buildableDef != null ) + if (def is BuildableDef buildableDef) { // if def built != def listed. - if ( thingDef?.entityDefToBuild != null ) + if (thingDef?.entityDefToBuild != null) { - _cachedDefIcons.Add( def, thingDef.entityDefToBuild.IconTexture() ); + _cachedDefIcons.Add(def, thingDef.entityDefToBuild.IconTexture()); return _cachedDefIcons[def]; } - _cachedDefIcons.Add( def, buildableDef.uiIcon ); + _cachedDefIcons.Add(def, buildableDef.uiIcon); return buildableDef.uiIcon; } // nothing stuck - _cachedDefIcons.Add( def, null ); + _cachedDefIcons.Add(def, null); return null; } } diff --git a/Source/Extensions/ResearchProjectDef_Extensions.cs b/Source/Extensions/ResearchProjectDef_Extensions.cs index 22f8505..ccd9c7c 100644 --- a/Source/Extensions/ResearchProjectDef_Extensions.cs +++ b/Source/Extensions/ResearchProjectDef_Extensions.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; -using System.Runtime.Remoting.Messaging; using Verse; namespace FluffyResearchTree @@ -13,7 +12,7 @@ public static class ResearchProjectDef_Extensions private static readonly Dictionary>> _unlocksCache = new Dictionary>>(); - public static List Descendants( this ResearchProjectDef research ) + public static List Descendants(this ResearchProjectDef research) { var descendants = new HashSet(); @@ -21,135 +20,135 @@ public static List Descendants( this ResearchProjectDef rese // populate initial queue var queue = new Queue( DefDatabase.AllDefsListForReading.Where( - res => res.prerequisites?.Contains( research ) ?? false ) ); + res => res.prerequisites?.Contains(research) ?? false)); // add to the list, and queue up children. - while ( queue.Count > 0 ) + while (queue.Count > 0) { var current = queue.Dequeue(); - descendants.Add( current ); + descendants.Add(current); - foreach ( var descendant in DefDatabase.AllDefsListForReading.Where( + foreach (var descendant in DefDatabase.AllDefsListForReading.Where( res => res.prerequisites?.Contains( - current ) ?? + current) ?? false && !descendants.Contains( - res ) ) ) - queue.Enqueue( descendant ); + res))) + queue.Enqueue(descendant); } return descendants.ToList(); } - public static IEnumerable GetPlantsUnlocked( this ResearchProjectDef research ) + public static IEnumerable GetPlantsUnlocked(this ResearchProjectDef research) { return DefDatabase.AllDefsListForReading .Where( - td => td.plant?.sowResearchPrerequisites?.Contains( research ) ?? false ); + td => td.plant?.sowResearchPrerequisites?.Contains(research) ?? false); } - public static List Ancestors( this ResearchProjectDef research ) + public static List Ancestors(this ResearchProjectDef research) { // keep a list of prerequites var prerequisites = new List(); - if ( research.prerequisites.NullOrEmpty() ) + if (research.prerequisites.NullOrEmpty()) return prerequisites; // keep a stack of prerequisites that should be checked - var stack = new Stack( research.prerequisites.Where( parent => parent != research ) ); + var stack = new Stack(research.prerequisites.Where(parent => parent != research)); // keep on checking everything on the stack until there is nothing left - while ( stack.Count > 0 ) + while (stack.Count > 0) { // add to list of prereqs var parent = stack.Pop(); - prerequisites.Add( parent ); + prerequisites.Add(parent); // add prerequitsite's prereqs to the stack - if ( !parent.prerequisites.NullOrEmpty() ) - foreach ( var grandparent in parent.prerequisites ) + if (!parent.prerequisites.NullOrEmpty()) + foreach (var grandparent in parent.prerequisites) // but only if not a prerequisite of itself, and not a cyclic prerequisite - if ( grandparent != parent && !prerequisites.Contains( grandparent ) ) - stack.Push( grandparent ); + if (grandparent != parent && !prerequisites.Contains(grandparent)) + stack.Push(grandparent); } return prerequisites.Distinct().ToList(); } - public static IEnumerable GetRecipesUnlocked( this ResearchProjectDef research ) + public static IEnumerable GetRecipesUnlocked(this ResearchProjectDef research) { // recipe directly locked behind research var direct = - DefDatabase.AllDefsListForReading.Where( rd => rd.researchPrerequisite == research ); + DefDatabase.AllDefsListForReading.Where(rd => rd.researchPrerequisite == research); // recipe building locked behind research var building = DefDatabase.AllDefsListForReading .Where( - td => ( td.researchPrerequisites?.Contains( research ) ?? false ) - && !td.AllRecipes.NullOrEmpty() ) - .SelectMany( td => td.AllRecipes ) - .Where( rd => rd.researchPrerequisite == null ); + td => (td.researchPrerequisites?.Contains(research) ?? false) + && !td.AllRecipes.NullOrEmpty()) + .SelectMany(td => td.AllRecipes) + .Where(rd => rd.researchPrerequisite == null); // return union of these two sets - return direct.Concat( building ).Distinct(); + return direct.Concat(building).Distinct(); } - public static IEnumerable GetTerrainUnlocked( this ResearchProjectDef research ) + public static IEnumerable GetTerrainUnlocked(this ResearchProjectDef research) { return DefDatabase.AllDefsListForReading - .Where( td => td.researchPrerequisites?.Contains( research ) ?? false ); + .Where(td => td.researchPrerequisites?.Contains(research) ?? false); } - public static IEnumerable GetThingsUnlocked( this ResearchProjectDef research ) + public static IEnumerable GetThingsUnlocked(this ResearchProjectDef research) { return DefDatabase.AllDefsListForReading - .Where( td => td.researchPrerequisites?.Contains( research ) ?? false ); + .Where(td => td.researchPrerequisites?.Contains(research) ?? false); } - public static List> GetUnlockDefsAndDescs( this ResearchProjectDef research, bool dedupe = true ) + public static List> GetUnlockDefsAndDescs(this ResearchProjectDef research, bool dedupe = true) { - if ( _unlocksCache.ContainsKey( research ) ) + if (_unlocksCache.ContainsKey(research)) return _unlocksCache[research]; var unlocks = new List>(); - unlocks.AddRange( research.GetThingsUnlocked() - .Where( d => d.IconTexture() != null ) - .Select( d => new Pair( - d, "Fluffy.ResearchTree.AllowsBuildingX".Translate( d.LabelCap ) ) ) ); - unlocks.AddRange( research.GetTerrainUnlocked() - .Where( d => d.IconTexture() != null ) - .Select( d => new Pair( - d, "Fluffy.ResearchTree.AllowsBuildingX".Translate( d.LabelCap ) ) ) ); - unlocks.AddRange( research.GetRecipesUnlocked() - .Where( d => d.IconTexture() != null ) - .Select( d => new Pair( - d, "Fluffy.ResearchTree.AllowsCraftingX".Translate( d.LabelCap ) ) ) ); - unlocks.AddRange( research.GetPlantsUnlocked() - .Where( d => d.IconTexture() != null ) - .Select( d => new Pair( - d, "Fluffy.ResearchTree.AllowsPlantingX".Translate( d.LabelCap ) ) ) ); + unlocks.AddRange(research.GetThingsUnlocked() + .Where(d => d.IconTexture() != null) + .Select(d => new Pair( + d, "Fluffy.ResearchTree.AllowsBuildingX".Translate(d.LabelCap)))); + unlocks.AddRange(research.GetTerrainUnlocked() + .Where(d => d.IconTexture() != null) + .Select(d => new Pair( + d, "Fluffy.ResearchTree.AllowsBuildingX".Translate(d.LabelCap)))); + unlocks.AddRange(research.GetRecipesUnlocked() + .Where(d => d.IconTexture() != null) + .Select(d => new Pair( + d, "Fluffy.ResearchTree.AllowsCraftingX".Translate(d.LabelCap)))); + unlocks.AddRange(research.GetPlantsUnlocked() + .Where(d => d.IconTexture() != null) + .Select(d => new Pair( + d, "Fluffy.ResearchTree.AllowsPlantingX".Translate(d.LabelCap)))); // get unlocks for all descendant research, and remove duplicates. var descendants = research.Descendants(); - if ( dedupe && descendants.Any() ) + if (dedupe && descendants.Any()) { var descendantUnlocks = research.Descendants() - .SelectMany( c => c.GetUnlockDefsAndDescs( false ).Select( u => u.First ) ) + .SelectMany(c => c.GetUnlockDefsAndDescs(false).Select(u => u.First)) .Distinct() .ToList(); - unlocks = unlocks.Where( u => !descendantUnlocks.Contains( u.First ) ).ToList(); + unlocks = unlocks.Where(u => !descendantUnlocks.Contains(u.First)).ToList(); } - _unlocksCache.Add( research, unlocks ); + _unlocksCache.Add(research, unlocks); return unlocks; } - public static ResearchNode ResearchNode( this ResearchProjectDef research ) + public static ResearchNode ResearchNode(this ResearchProjectDef research) { - var node = Tree.Nodes.OfType().FirstOrDefault( n => n.Research == research ); - if ( node == null ) - Log.Error( "Node for {0} not found. Was it intentionally hidden or locked?", true, research.LabelCap ); + var node = Tree.Nodes.OfType().FirstOrDefault(n => n.Research == research); + if (node == null) + Log.Error("Node for {0} not found. Was it intentionally hidden or locked?", true, research.LabelCap); return node; } } diff --git a/Source/FloatMenu_Fixed.cs b/Source/FloatMenu_Fixed.cs index c8629a8..5dcb3c2 100644 --- a/Source/FloatMenu_Fixed.cs +++ b/Source/FloatMenu_Fixed.cs @@ -11,19 +11,19 @@ public class FloatMenu_Fixed : FloatMenu { private readonly Vector2 _position; - public FloatMenu_Fixed( List options, Vector2 position, bool focus = false ) : base( options ) + public FloatMenu_Fixed(List options, Vector2 position, bool focus = false) : base(options) { - _position = position; + _position = position; vanishIfMouseDistant = false; - focusWhenOpened = focus; + focusWhenOpened = focus; } protected override void SetInitialSizeAndPosition() { - var position = _position; - if ( position.x + InitialSize.x > UI.screenWidth ) position.x = UI.screenWidth - InitialSize.x; - if ( position.y + InitialSize.y > UI.screenHeight ) position.y = UI.screenHeight - InitialSize.y; - windowRect = new Rect( position.x, position.y, InitialSize.x, InitialSize.y ); + var position = _position; + if (position.x + InitialSize.x > UI.screenWidth) position.x = UI.screenWidth - InitialSize.x; + if (position.y + InitialSize.y > UI.screenHeight) position.y = UI.screenHeight - InitialSize.y; + windowRect = new Rect(position.x, position.y, InitialSize.x, InitialSize.y); } } } \ No newline at end of file diff --git a/Source/Graph/DummyNode.cs b/Source/Graph/DummyNode.cs index 1864005..6a9b16e 100644 --- a/Source/Graph/DummyNode.cs +++ b/Source/Graph/DummyNode.cs @@ -10,7 +10,7 @@ public class DummyNode : Node { #region Overrides of Node - public override string Label => "DUMMY: " + ( Parent?.Label ?? "??" ) + " -> " + ( Child?.Label ?? "??" ); + public override string Label => "DUMMY: " + (Parent?.Label ?? "??") + " -> " + (Child?.Label ?? "??"); #endregion @@ -41,8 +41,7 @@ public ResearchNode Parent { get { - var parent = InNodes.FirstOrDefault() as ResearchNode; - if ( parent != null ) + if (InNodes.FirstOrDefault() is ResearchNode parent) return parent; var dummyParent = InNodes.FirstOrDefault() as DummyNode; @@ -55,8 +54,7 @@ public ResearchNode Child { get { - var child = OutNodes.FirstOrDefault() as ResearchNode; - if ( child != null ) + if (OutNodes.FirstOrDefault() is ResearchNode child) return child; var dummyChild = OutNodes.FirstOrDefault() as DummyNode; @@ -65,10 +63,10 @@ public ResearchNode Child } } - public override bool Completed => OutNodes.FirstOrDefault()?.Completed ?? false; - public override bool Available => OutNodes.FirstOrDefault()?.Available ?? false; - public override bool Highlighted => OutNodes.FirstOrDefault()?.Highlighted ?? false; - public override Color Color => OutNodes.FirstOrDefault()?.Color ?? Color.white; - public override Color EdgeColor => OutNodes.FirstOrDefault()?.EdgeColor ?? Color.white; + public override bool Completed => OutNodes.FirstOrDefault()?.Completed ?? false; + public override bool Available => OutNodes.FirstOrDefault()?.Available ?? false; + public override bool Highlighted => OutNodes.FirstOrDefault()?.Highlighted ?? false; + public override Color Color => OutNodes.FirstOrDefault()?.Color ?? Color.white; + public override Color EdgeColor => OutNodes.FirstOrDefault()?.EdgeColor ?? Color.white; } } \ No newline at end of file diff --git a/Source/Graph/Edge.cs b/Source/Graph/Edge.cs index ee0747b..6989082 100644 --- a/Source/Graph/Edge.cs +++ b/Source/Graph/Edge.cs @@ -13,10 +13,10 @@ public class Edge where T1 : Node where T2 : Node private T1 _in; private T2 _out; - public Edge( T1 @in, T2 @out ) + public Edge(T1 @in, T2 @out) { - _in = @in; - _out = @out; + _in = @in; + _out = @out; IsDummy = _out is DummyNode; } @@ -25,7 +25,7 @@ public T1 In get => _in; set { - _in = value; + _in = value; IsDummy = _out is DummyNode; } } @@ -35,53 +35,53 @@ public T2 Out get => _out; set { - _out = value; + _out = value; IsDummy = _out is DummyNode; } } - public int Span => _out.X - _in.X; - public float Length => Mathf.Abs( _in.Yf - _out.Yf ) * ( IsDummy ? 10 : 1 ); - public bool IsDummy { get; private set; } + public int Span => _out.X - _in.X; + public float Length => Mathf.Abs(_in.Yf - _out.Yf) * (IsDummy ? 10 : 1); + public bool IsDummy { get; private set; } public int DrawOrder { get { - if ( Out.Highlighted ) + if (Out.Highlighted) return 3; - if ( Out.Completed ) + if (Out.Completed) return 2; - if ( Out.Available ) + if (Out.Available) return 1; return 0; } } - public void Draw( Rect visibleRect ) + public void Draw(Rect visibleRect) { - if ( !In.IsVisible( visibleRect ) && !Out.IsVisible( visibleRect ) ) + if (!In.IsVisible(visibleRect) && !Out.IsVisible(visibleRect)) return; var color = Out.EdgeColor; GUI.color = color; - var left = In.Right; + var left = In.Right; var right = Out.Left; // if left and right are on the same level, just draw a straight line. - if ( Math.Abs( left.y - right.y ) < Epsilon ) + if (Math.Abs(left.y - right.y) < Epsilon) { - var line = new Rect( left.x, left.y - 2f, right.x - left.x, 4f ); - GUI.DrawTexture( line, Lines.EW ); + var line = new Rect(left.x, left.y - 2f, right.x - left.x, 4f); + GUI.DrawTexture(line, Lines.EW); } // draw three line pieces and two curves. else { // determine top and bottom y positions - var top = Math.Min( left.y, right.y ) + NodeMargins.x / 4f; - var bottom = Math.Max( left.y, right.y ) - NodeMargins.x / 4f; + var top = Math.Min(left.y, right.y) + NodeMargins.x / 4f; + var bottom = Math.Max(left.y, right.y) - NodeMargins.x / 4f; // straight bits // left to curve @@ -89,64 +89,64 @@ public void Draw( Rect visibleRect ) left.x, left.y - 2f, NodeMargins.x / 4f, - 4f ); - GUI.DrawTexture( leftToCurve, Lines.EW ); + 4f); + GUI.DrawTexture(leftToCurve, Lines.EW); // curve to curve var curveToCurve = new Rect( left.x + NodeMargins.x / 2f - 2f, top, 4f, - bottom - top ); - GUI.DrawTexture( curveToCurve, Lines.NS ); + bottom - top); + GUI.DrawTexture(curveToCurve, Lines.NS); // curve to right var curveToRight = new Rect( - left.x + NodeMargins.x / 4f * 3, - right.y - 2f, + left.x + NodeMargins.x / 4f * 3, + right.y - 2f, right.x - left.x - NodeMargins.x / 4f * 3, - 4f ); - GUI.DrawTexture( curveToRight, Lines.EW ); + 4f); + GUI.DrawTexture(curveToRight, Lines.EW); // curve positions var curveLeft = new Rect( left.x + NodeMargins.x / 4f, left.y - NodeMargins.x / 4f, NodeMargins.x / 2f, - NodeMargins.x / 2f ); + NodeMargins.x / 2f); var curveRight = new Rect( - left.x + NodeMargins.x / 4f, + left.x + NodeMargins.x / 4f, right.y - NodeMargins.x / 4f, NodeMargins.x / 2f, - NodeMargins.x / 2f ); + NodeMargins.x / 2f); // going down - if ( left.y < right.y ) + if (left.y < right.y) { - GUI.DrawTextureWithTexCoords( curveLeft, Lines.Circle, new Rect( 0.5f, 0.5f, 0.5f, 0.5f ) ); + GUI.DrawTextureWithTexCoords(curveLeft, Lines.Circle, new Rect(0.5f, 0.5f, 0.5f, 0.5f)); // bottom right quadrant - GUI.DrawTextureWithTexCoords( curveRight, Lines.Circle, new Rect( 0f, 0f, 0.5f, 0.5f ) ); + GUI.DrawTextureWithTexCoords(curveRight, Lines.Circle, new Rect(0f, 0f, 0.5f, 0.5f)); // top left quadrant } // going up else { - GUI.DrawTextureWithTexCoords( curveLeft, Lines.Circle, new Rect( 0.5f, 0f, 0.5f, 0.5f ) ); + GUI.DrawTextureWithTexCoords(curveLeft, Lines.Circle, new Rect(0.5f, 0f, 0.5f, 0.5f)); // top right quadrant - GUI.DrawTextureWithTexCoords( curveRight, Lines.Circle, new Rect( 0f, 0.5f, 0.5f, 0.5f ) ); + GUI.DrawTextureWithTexCoords(curveRight, Lines.Circle, new Rect(0f, 0.5f, 0.5f, 0.5f)); // bottom left quadrant } } // draw the end arrow (if not dummy) - if ( !IsDummy ) + if (!IsDummy) { var end = new Rect( right.x - 16f, right.y - 8f, 16f, - 16f ); - GUI.DrawTexture( end, Lines.End ); + 16f); + GUI.DrawTexture(end, Lines.End); } // or draw a line piece through the dummy @@ -158,7 +158,7 @@ public void Draw( Rect visibleRect ) NodeSize.x, 4f ); - GUI.DrawTexture( through, Lines.EW ); + GUI.DrawTexture(through, Lines.EW); } // reset color diff --git a/Source/Graph/Node.cs b/Source/Graph/Node.cs index f18bfde..70bb427 100644 --- a/Source/Graph/Node.cs +++ b/Source/Graph/Node.cs @@ -15,11 +15,11 @@ namespace FluffyResearchTree { public class Node { - protected const float Offset = 2f; - protected List> _inEdges = new List>(); - protected bool _largeLabel; - protected List> _outEdges = new List>(); - protected Vector2 _pos = Vector2.zero; + protected const float Offset = 2f; + protected List> _inEdges = new List>(); + protected bool _largeLabel; + protected List> _outEdges = new List>(); + protected Vector2 _pos = Vector2.zero; protected Rect _queueRect, @@ -33,26 +33,26 @@ protected Rect protected bool _rectsSet; protected Vector2 _topLeft = Vector2.zero, - _right = Vector2.zero, - _left = Vector2.zero; + _right = Vector2.zero, + _left = Vector2.zero; public List Descendants { - get { return OutNodes.Concat( OutNodes.SelectMany( n => n.Descendants ) ).ToList(); } + get { return OutNodes.Concat(OutNodes.SelectMany(n => n.Descendants)).ToList(); } } public List> OutEdges => _outEdges; - public List OutNodes => _outEdges.Select( e => e.Out ).ToList(); - public List> InEdges => _inEdges; - public List InNodes => _inEdges.Select( e => e.In ).ToList(); - public List> Edges => _inEdges.Concat( _outEdges ).ToList(); - public List Nodes => InNodes.Concat( OutNodes ).ToList(); + public List OutNodes => _outEdges.Select(e => e.Out).ToList(); + public List> InEdges => _inEdges; + public List InNodes => _inEdges.Select(e => e.In).ToList(); + public List> Edges => _inEdges.Concat(_outEdges).ToList(); + public List Nodes => InNodes.Concat(OutNodes).ToList(); public Rect CostIconRect { get { - if ( !_rectsSet ) + if (!_rectsSet) SetRects(); return _costIconRect; @@ -63,21 +63,21 @@ public Rect CostLabelRect { get { - if ( !_rectsSet ) + if (!_rectsSet) SetRects(); return _costLabelRect; } } - public virtual Color Color => Color.white; + public virtual Color Color => Color.white; public virtual Color EdgeColor => Color; public Rect IconsRect { get { - if ( !_rectsSet ) + if (!_rectsSet) SetRects(); return _iconsRect; @@ -88,7 +88,7 @@ public Rect LabelRect { get { - if ( !_rectsSet ) + if (!_rectsSet) SetRects(); return _labelRect; @@ -102,7 +102,7 @@ public Vector2 Left { get { - if ( !_rectsSet ) + if (!_rectsSet) SetRects(); return _left; @@ -116,7 +116,7 @@ public Rect QueueRect { get { - if ( !_rectsSet ) + if (!_rectsSet) SetRects(); return _queueRect; @@ -127,7 +127,7 @@ public Rect LockRect { get { - if ( !_rectsSet ) + if (!_rectsSet) SetRects(); return _lockRect; @@ -141,7 +141,7 @@ public Rect Rect { get { - if ( !_rectsSet ) + if (!_rectsSet) SetRects(); return _rect; @@ -155,88 +155,88 @@ public Vector2 Right { get { - if ( !_rectsSet ) + if (!_rectsSet) SetRects(); return _right; } } - public Vector2 Center => ( Left + Right ) / 2f; + public Vector2 Center => (Left + Right) / 2f; public virtual int X { - get => (int) _pos.x; + get => (int)_pos.x; set { - if ( value < 0 ) - throw new ArgumentOutOfRangeException( nameof( value ) ); - if ( Math.Abs( _pos.x - value ) < Epsilon ) + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value)); + if (Math.Abs(_pos.x - value) < Epsilon) return; - Log.Trace( "\t" + this + " X: " + _pos.x + " -> " + value ); + Log.Trace("\t" + this + " X: " + _pos.x + " -> " + value); _pos.x = value; // update caches - _rectsSet = false; - Tree.Size.x = Tree.Nodes.Max( n => n.X ); + _rectsSet = false; + Tree.Size.x = Tree.Nodes.Max(n => n.X); Tree.OrderDirty = true; } } public virtual int Y { - get => (int) _pos.y; + get => (int)_pos.y; set { - if ( value < 0 ) - throw new ArgumentOutOfRangeException( nameof( value ) ); - if ( Math.Abs( _pos.y - value ) < Epsilon ) + if (value < 0) + throw new ArgumentOutOfRangeException(nameof(value)); + if (Math.Abs(_pos.y - value) < Epsilon) return; - Log.Trace( "\t" + this + " Y: " + _pos.y + " -> " + value ); + Log.Trace("\t" + this + " Y: " + _pos.y + " -> " + value); _pos.y = value; // update caches - _rectsSet = false; - Tree.Size.z = Tree.Nodes.Max( n => n.Y ); + _rectsSet = false; + Tree.Size.z = Tree.Nodes.Max(n => n.Y); Tree.OrderDirty = true; } } - public virtual Vector2 Pos => new Vector2( X, Y ); + public virtual Vector2 Pos => new Vector2(X, Y); public virtual float Yf { get => _pos.y; set { - if ( Math.Abs( _pos.y - value ) < Epsilon ) + if (Math.Abs(_pos.y - value) < Epsilon) return; _pos.y = value; // update caches - Tree.Size.z = Tree.Nodes.Max( n => n.Y ) + 1; + Tree.Size.z = Tree.Nodes.Max(n => n.Y) + 1; Tree.OrderDirty = true; } } public virtual string Label { get; } - public virtual bool Completed => false; - public virtual bool Available => false; + public virtual bool Completed => false; + public virtual bool Available => false; public virtual bool Highlighted { get; set; } - protected internal virtual bool SetDepth( int min = 1 ) + protected internal virtual bool SetDepth(int min = 1) { // calculate desired position - var isRoot = InNodes.NullOrEmpty(); - var desired = isRoot ? 1 : InNodes.Max( n => n.X ) + 1; - var depth = Mathf.Max( desired, min ); + var isRoot = InNodes.NullOrEmpty(); + var desired = isRoot ? 1 : InNodes.Max(n => n.X) + 1; + var depth = Mathf.Max(desired, min); // no change - if ( depth == X ) + if (depth == X) return false; // update @@ -250,15 +250,15 @@ protected internal virtual bool SetDepth( int min = 1 ) public virtual void Debug() { var text = new StringBuilder(); - text.AppendLine( Label + " (" + X + ", " + Y + "):" ); - text.AppendLine( "- Parents" ); - foreach ( var parent in InNodes ) text.AppendLine( "-- " + parent.Label ); + text.AppendLine(Label + " (" + X + ", " + Y + "):"); + text.AppendLine("- Parents"); + foreach (var parent in InNodes) text.AppendLine("-- " + parent.Label); - text.AppendLine( "- Children" ); - foreach ( var child in OutNodes ) text.AppendLine( "-- " + child.Label ); + text.AppendLine("- Children"); + foreach (var child in OutNodes) text.AppendLine("-- " + child.Label); - text.AppendLine( "" ); - Log.Message( text.ToString() ); + text.AppendLine(""); + Log.Message(text.ToString()); } @@ -271,75 +271,75 @@ public void SetRects() { // origin _topLeft = new Vector2( - ( X - 1 ) * ( NodeSize.x + NodeMargins.x ), - ( Yf - 1 ) * ( NodeSize.y + NodeMargins.y ) ); + (X - 1) * (NodeSize.x + NodeMargins.x), + (Yf - 1) * (NodeSize.y + NodeMargins.y)); - SetRects( _topLeft ); + SetRects(_topLeft); } - public void SetRects( Vector2 topLeft ) + public void SetRects(Vector2 topLeft) { // main rect - _rect = new Rect( topLeft.x, + _rect = new Rect(topLeft.x, topLeft.y, NodeSize.x, - NodeSize.y ); + NodeSize.y); // left and right edges - _left = new Vector2( _rect.xMin, _rect.yMin + _rect.height / 2f ); - _right = new Vector2( _rect.xMax, _left.y ); + _left = new Vector2(_rect.xMin, _rect.yMin + _rect.height / 2f); + _right = new Vector2(_rect.xMax, _left.y); // queue rect - _queueRect = new Rect( _rect.xMax - QueueLabelSize / 2f, - _rect.yMin + ( _rect.height - QueueLabelSize ) / 2f, QueueLabelSize, - QueueLabelSize ); + _queueRect = new Rect(_rect.xMax - QueueLabelSize / 2f, + _rect.yMin + (_rect.height - QueueLabelSize) / 2f, QueueLabelSize, + QueueLabelSize); // label rect - _labelRect = new Rect( _rect.xMin + 6f, - _rect.yMin + 3f, - _rect.width * 2f / 3f - 6f, - _rect.height * .5f - 3f ); + _labelRect = new Rect(_rect.xMin + 6f, + _rect.yMin + 3f, + _rect.width * 2f / 3f - 6f, + _rect.height * .5f - 3f); // research cost rect - _costLabelRect = new Rect( _rect.xMin + _rect.width * 2f / 3f, - _rect.yMin + 3f, + _costLabelRect = new Rect(_rect.xMin + _rect.width * 2f / 3f, + _rect.yMin + 3f, _rect.width * 1f / 3f - 16f - 3f, - _rect.height * .5f - 3f ); + _rect.height * .5f - 3f); // research icon rect - _costIconRect = new Rect( _costLabelRect.xMax, - _rect.yMin + ( _costLabelRect.height - 16f ) / 2, + _costIconRect = new Rect(_costLabelRect.xMax, + _rect.yMin + (_costLabelRect.height - 16f) / 2, 16f, - 16f ); + 16f); // icon container rect - _iconsRect = new Rect( _rect.xMin, + _iconsRect = new Rect(_rect.xMin, _rect.yMin + _rect.height * .5f, _rect.width, - _rect.height * .5f ); + _rect.height * .5f); // lock icon rect - _lockRect = new Rect( 0f, 0f, 32f, 32f ); - _lockRect = _lockRect.CenteredOnXIn( _rect ); - _lockRect = _lockRect.CenteredOnYIn( _rect ); + _lockRect = new Rect(0f, 0f, 32f, 32f); + _lockRect = _lockRect.CenteredOnXIn(_rect); + _lockRect = _lockRect.CenteredOnYIn(_rect); // see if the label is too big - _largeLabel = Text.CalcHeight( Label, _labelRect.width ) > _labelRect.height; + _largeLabel = Text.CalcHeight(Label, _labelRect.width) > _labelRect.height; // done _rectsSet = true; } - public virtual bool IsVisible( Rect visibleRect ) + public virtual bool IsVisible(Rect visibleRect) { return !( Rect.xMin > visibleRect.xMax || Rect.xMax < visibleRect.xMin || Rect.yMin > visibleRect.yMax || - Rect.yMax < visibleRect.yMin ); + Rect.yMax < visibleRect.yMin); } - public virtual void Draw( Rect visibleRect, bool forceDetailedMode = false ) + public virtual void Draw(Rect visibleRect, bool forceDetailedMode = false) { } } diff --git a/Source/Graph/ResearchNode.cs b/Source/Graph/ResearchNode.cs index ae032aa..67f0e69 100644 --- a/Source/Graph/ResearchNode.cs +++ b/Source/Graph/ResearchNode.cs @@ -1,11 +1,11 @@ // ResearchNode.cs // Copyright Karel Kroeze, 2020-2020 +using RimWorld; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; -using RimWorld; using UnityEngine; using Verse; using static FluffyResearchTree.Constants; @@ -22,12 +22,12 @@ public class ResearchNode : Node public ResearchProjectDef Research; - public ResearchNode( ResearchProjectDef research ) + public ResearchNode(ResearchProjectDef research) { Research = research; // initialize position at vanilla y position, leave x at zero - we'll determine this ourselves - _pos = new Vector2( 0, research.researchViewY + 1 ); + _pos = new Vector2(0, research.researchViewY + 1); } public List Parents @@ -35,7 +35,7 @@ public List Parents get { var parents = InNodes.OfType(); - parents.Concat( InNodes.OfType().Select( dn => dn.Parent ) ); + parents.Concat(InNodes.OfType().Select(dn => dn.Parent)); return parents.ToList(); } } @@ -44,11 +44,11 @@ public override Color Color { get { - if ( Highlighted ) + if (Highlighted) return GenUI.MouseoverColor; - if ( Completed ) + if (Completed) return Assets.ColorCompleted[Research.techLevel]; - if ( Available ) + if (Available) return Assets.ColorCompleted[Research.techLevel]; return Assets.ColorUnavailable[Research.techLevel]; } @@ -58,11 +58,11 @@ public override Color EdgeColor { get { - if ( Highlighted ) + if (Highlighted) return GenUI.MouseoverColor; - if ( Completed ) + if (Completed) return Assets.ColorCompleted[Research.techLevel]; - if ( Available ) + if (Available) return Assets.ColorAvailable[Research.techLevel]; return Assets.ColorUnavailable[Research.techLevel]; } @@ -73,40 +73,39 @@ public List Children get { var children = OutNodes.OfType(); - children.Concat( OutNodes.OfType().Select( dn => dn.Child ) ); + children.Concat(OutNodes.OfType().Select(dn => dn.Child)); return children.ToList(); } } public override string Label => Research.LabelCap; - public static bool BuildingPresent( ResearchProjectDef research ) + public static bool BuildingPresent(ResearchProjectDef research) { - if ( DebugSettings.godMode && Prefs.DevMode ) + if (DebugSettings.godMode && Prefs.DevMode) return true; // try get from cache - bool result; - if ( _buildingPresentCache.TryGetValue( research, out result ) ) + if (_buildingPresentCache.TryGetValue(research, out bool result)) return result; // do the work manually - if ( research.requiredResearchBuilding == null ) + if (research.requiredResearchBuilding == null) result = true; else - result = Find.Maps.SelectMany( map => map.listerBuildings.allBuildingsColonist ) + result = Find.Maps.SelectMany(map => map.listerBuildings.allBuildingsColonist) .OfType() - .Any( b => research.CanBeResearchedAt( b, true ) ); + .Any(b => research.CanBeResearchedAt(b, true)); - if ( result ) - result = research.Ancestors().All( BuildingPresent ); + if (result) + result = research.Ancestors().All(BuildingPresent); // update cache - _buildingPresentCache.Add( research, result ); + _buildingPresentCache.Add(research, result); return result; } - public static bool TechprintAvailable( ResearchProjectDef research ) + public static bool TechprintAvailable(ResearchProjectDef research) { return research.TechprintRequirementMet; } @@ -117,83 +116,82 @@ public static void ClearCaches() _missingFacilitiesCache.Clear(); } - public static implicit operator ResearchNode( ResearchProjectDef def ) + public static implicit operator ResearchNode(ResearchProjectDef def) { return def.ResearchNode(); } - public int Matches( string query ) + public int Matches(string query) { var culture = CultureInfo.CurrentUICulture; - query = query.ToLower( culture ); + query = query.ToLower(culture); - if ( Research.LabelCap.RawText.ToLower( culture ).Contains( query ) ) + if (Research.LabelCap.RawText.ToLower(culture).Contains(query)) return 1; - if ( Research.GetUnlockDefsAndDescs() - .Any( unlock => unlock.First.LabelCap.RawText.ToLower( culture ).Contains( query ) ) ) + if (Research.GetUnlockDefsAndDescs() + .Any(unlock => unlock.First.LabelCap.RawText.ToLower(culture).Contains(query))) return 2; - if ( Research.description.ToLower( culture ).Contains( query ) ) + if (Research.description.ToLower(culture).Contains(query)) return 3; return 0; } - public static List MissingFacilities( ResearchProjectDef research ) + public static List MissingFacilities(ResearchProjectDef research) { // try get from cache - List missing; - if ( _missingFacilitiesCache.TryGetValue( research, out missing ) ) + if (_missingFacilitiesCache.TryGetValue(research, out List missing)) return missing; // get list of all researches required before this - var thisAndPrerequisites = research.Ancestors().Where( rpd => !rpd.IsFinished ).ToList(); - thisAndPrerequisites.Add( research ); + var thisAndPrerequisites = research.Ancestors().Where(rpd => !rpd.IsFinished).ToList(); + thisAndPrerequisites.Add(research); // get list of all available research benches - var availableBenches = Find.Maps.SelectMany( map => map.listerBuildings.allBuildingsColonist ) + var availableBenches = Find.Maps.SelectMany(map => map.listerBuildings.allBuildingsColonist) .OfType(); - var availableBenchDefs = availableBenches.Select( b => b.def ).Distinct(); + var availableBenchDefs = availableBenches.Select(b => b.def).Distinct(); missing = new List(); // check each for prerequisites // TODO: We should really build this list recursively so we can re-use results for prerequisites. - foreach ( var rpd in thisAndPrerequisites ) + foreach (var rpd in thisAndPrerequisites) { - if ( rpd.requiredResearchBuilding == null ) + if (rpd.requiredResearchBuilding == null) continue; - if ( !availableBenchDefs.Contains( rpd.requiredResearchBuilding ) ) - missing.Add( rpd.requiredResearchBuilding ); + if (!availableBenchDefs.Contains(rpd.requiredResearchBuilding)) + missing.Add(rpd.requiredResearchBuilding); - if ( rpd.requiredResearchFacilities.NullOrEmpty() ) + if (rpd.requiredResearchFacilities.NullOrEmpty()) continue; - foreach ( var facility in rpd.requiredResearchFacilities ) - if ( !availableBenches.Any( b => b.HasFacility( facility ) ) ) - missing.Add( facility ); + foreach (var facility in rpd.requiredResearchFacilities) + if (!availableBenches.Any(b => b.HasFacility(facility))) + missing.Add(facility); } // add to cache missing = missing.Distinct().ToList(); - _missingFacilitiesCache.Add( research, missing ); + _missingFacilitiesCache.Add(research, missing); return missing; } public bool BuildingPresent() { - return BuildingPresent( Research ); + return BuildingPresent(Research); } - + public bool TechprintAvailable() { - return TechprintAvailable( Research ); + return TechprintAvailable(Research); } /// /// Draw the node, including interactions. /// - public override void Draw( Rect visibleRect, bool forceDetailedMode = false ) + public override void Draw(Rect visibleRect, bool forceDetailedMode = false) { - if ( !IsVisible( visibleRect ) ) + if (!IsVisible(visibleRect)) { Highlighted = false; return; @@ -201,149 +199,149 @@ public override void Draw( Rect visibleRect, bool forceDetailedMode = false ) var detailedMode = forceDetailedMode || MainTabWindow_ResearchTree.Instance.ZoomLevel < DetailedModeZoomLevelCutoff; - var mouseOver = Mouse.IsOver( Rect ); - if ( Event.current.type == EventType.Repaint ) + var mouseOver = Mouse.IsOver(Rect); + if (Event.current.type == EventType.Repaint) { // researches that are completed or could be started immediately, and that have the required building(s) available GUI.color = mouseOver ? GenUI.MouseoverColor : Color; - if ( mouseOver || Highlighted ) - GUI.DrawTexture( Rect, Assets.ButtonActive ); + if (mouseOver || Highlighted) + GUI.DrawTexture(Rect, Assets.ButtonActive); else - GUI.DrawTexture( Rect, Assets.Button ); + GUI.DrawTexture(Rect, Assets.Button); // grey out center to create a progress bar effect, completely greying out research not started. - if ( Available ) + if (Available) { - var progressBarRect = Rect.ContractedBy( 3f ); - GUI.color = Assets.ColorAvailable[Research.techLevel]; + var progressBarRect = Rect.ContractedBy(3f); + GUI.color = Assets.ColorAvailable[Research.techLevel]; progressBarRect.xMin += Research.ProgressPercent * progressBarRect.width; - GUI.DrawTexture( progressBarRect, BaseContent.WhiteTex ); + GUI.DrawTexture(progressBarRect, BaseContent.WhiteTex); } Highlighted = false; // draw the research label - if ( !Completed && !Available ) + if (!Completed && !Available) GUI.color = Color.grey; else GUI.color = Color.white; - if ( detailedMode ) + if (detailedMode) { - Text.Anchor = TextAnchor.UpperLeft; + Text.Anchor = TextAnchor.UpperLeft; Text.WordWrap = false; - Text.Font = _largeLabel ? GameFont.Tiny : GameFont.Small; - Widgets.Label( LabelRect, Research.LabelCap ); + Text.Font = _largeLabel ? GameFont.Tiny : GameFont.Small; + Widgets.Label(LabelRect, Research.LabelCap); } else { - Text.Anchor = TextAnchor.MiddleCenter; + Text.Anchor = TextAnchor.MiddleCenter; Text.WordWrap = false; - Text.Font = GameFont.Medium; - Widgets.Label( Rect, Research.LabelCap ); + Text.Font = GameFont.Medium; + Widgets.Label(Rect, Research.LabelCap); } // draw research cost and icon - if ( detailedMode ) + if (detailedMode) { Text.Anchor = TextAnchor.UpperRight; - Text.Font = Research.CostApparent > 1000000 ? GameFont.Tiny : GameFont.Small; - Widgets.Label( CostLabelRect, Research.CostApparent.ToStringByStyle( ToStringStyle.Integer ) ); - GUI.DrawTexture( CostIconRect, !Completed && !Available ? Assets.Lock : Assets.ResearchIcon, - ScaleMode.ScaleToFit ); + Text.Font = Research.CostApparent > 1000000 ? GameFont.Tiny : GameFont.Small; + Widgets.Label(CostLabelRect, Research.CostApparent.ToStringByStyle(ToStringStyle.Integer)); + GUI.DrawTexture(CostIconRect, !Completed && !Available ? Assets.Lock : Assets.ResearchIcon, + ScaleMode.ScaleToFit); } Text.WordWrap = true; // attach description and further info to a tooltip - TooltipHandler.TipRegion( Rect, GetResearchTooltipString, Research.GetHashCode() ); - if ( !BuildingPresent() ) - TooltipHandler.TipRegion( Rect, "Fluffy.ResearchTree.MissingFacilities".Translate( - string.Join( ", ", + TooltipHandler.TipRegion(Rect, GetResearchTooltipString, Research.GetHashCode()); + if (!BuildingPresent()) + TooltipHandler.TipRegion(Rect, "Fluffy.ResearchTree.MissingFacilities".Translate( + string.Join(", ", MissingFacilities() - .Select( td => td.LabelCap ).ToArray() ) ) ); + .Select(td => td.LabelCap).ToArray()))); - else if ( !TechprintAvailable() ) - TooltipHandler.TipRegion( Rect, "Fluffy.ResearchTree.MissingTechprints".Translate( - Research.TechprintsApplied, Research.techprintCount ) ); + else if (!TechprintAvailable()) + TooltipHandler.TipRegion(Rect, "Fluffy.ResearchTree.MissingTechprints".Translate( + Research.TechprintsApplied, Research.techprintCount)); // draw unlock icons - if ( detailedMode ) + if (detailedMode) { var unlocks = Research.GetUnlockDefsAndDescs(); - for ( var i = 0; i < unlocks.Count; i++ ) + for (var i = 0; i < unlocks.Count; i++) { var iconRect = new Rect( - IconsRect.xMax - ( i + 1 ) * ( IconSize.x + 4f ), - IconsRect.yMin + ( IconsRect.height - IconSize.y ) / 2f, + IconsRect.xMax - (i + 1) * (IconSize.x + 4f), + IconsRect.yMin + (IconsRect.height - IconSize.y) / 2f, IconSize.x, - IconSize.y ); + IconSize.y); - if ( iconRect.xMin - IconSize.x < IconsRect.xMin && - i + 1 < unlocks.Count ) + if (iconRect.xMin - IconSize.x < IconsRect.xMin && + i + 1 < unlocks.Count) { // stop the loop if we're about to overflow and have 2 or more unlocks yet to print. iconRect.x = IconsRect.x + 4f; - GUI.DrawTexture( iconRect, Assets.MoreIcon, ScaleMode.ScaleToFit ); - var tip = string.Join( "\n", - unlocks.GetRange( i, unlocks.Count - i ).Select( p => p.Second ) - .ToArray() ); - TooltipHandler.TipRegion( iconRect, tip ); + GUI.DrawTexture(iconRect, Assets.MoreIcon, ScaleMode.ScaleToFit); + var tip = string.Join("\n", + unlocks.GetRange(i, unlocks.Count - i).Select(p => p.Second) + .ToArray()); + TooltipHandler.TipRegion(iconRect, tip); // new TipSignal( tip, Settings.TipID, TooltipPriority.Pawn ) ); break; } // draw icon - unlocks[i].First.DrawColouredIcon( iconRect ); + unlocks[i].First.DrawColouredIcon(iconRect); // tooltip - TooltipHandler.TipRegion( iconRect, unlocks[i].Second ); + TooltipHandler.TipRegion(iconRect, unlocks[i].Second); } } - if ( mouseOver ) + if (mouseOver) { // highlight prerequisites if research available - if ( Available ) + if (Available) { Highlighted = true; - foreach ( var prerequisite in GetMissingRequiredRecursive() ) + foreach (var prerequisite in GetMissingRequiredRecursive()) prerequisite.Highlighted = true; } // highlight children if completed - else if ( Completed ) + else if (Completed) { - foreach ( var child in Children ) + foreach (var child in Children) child.Highlighted = true; } } } // if clicked and not yet finished, queue up this research and all prereqs. - if ( Widgets.ButtonInvisible( Rect ) && Available ) + if (Widgets.ButtonInvisible(Rect) && Available) { // LMB is queue operations, RMB is info - if ( Event.current.button == 0 && !Research.IsFinished ) + if (Event.current.button == 0 && !Research.IsFinished) { - if ( !Queue.IsQueued( this ) ) + if (!Queue.IsQueued(this)) { // if shift is held, add to queue, otherwise replace queue var queue = GetMissingRequiredRecursive() - .Concat( new List( new[] {this} ) ) + .Concat(new List(new[] { this })) .Distinct(); - Queue.EnqueueRange( queue, Event.current.shift ); + Queue.EnqueueRange(queue, Event.current.shift); } else { - Queue.Dequeue( this ); + Queue.Dequeue(this); } } - if ( DebugSettings.godMode && Prefs.DevMode && Event.current.button == 1 && !Research.IsFinished ) + if (DebugSettings.godMode && Prefs.DevMode && Event.current.button == 1 && !Research.IsFinished) { - Find.ResearchManager.FinishProject( Research ); + Find.ResearchManager.FinishProject(Research); Queue.Notify_InstantFinished(); } } @@ -355,22 +353,22 @@ public override void Draw( Rect visibleRect, bool forceDetailedMode = false ) /// List prerequisites public List GetMissingRequiredRecursive() { - var parents = Research.prerequisites?.Where( rpd => !rpd.IsFinished ).Select( rpd => rpd.ResearchNode() ); - if ( parents == null ) + var parents = Research.prerequisites?.Where(rpd => !rpd.IsFinished).Select(rpd => rpd.ResearchNode()); + if (parents == null) return new List(); - var allParents = new List( parents ); - foreach ( var parent in parents ) - allParents.AddRange( parent.GetMissingRequiredRecursive() ); + var allParents = new List(parents); + foreach (var parent in parents) + allParents.AddRange(parent.GetMissingRequiredRecursive()); return allParents.Distinct().ToList(); } public override bool Completed => Research.IsFinished; - public override bool Available => !Research.IsFinished && ( DebugSettings.godMode || (BuildingPresent() && TechprintAvailable())); + public override bool Available => !Research.IsFinished && (DebugSettings.godMode || (BuildingPresent() && TechprintAvailable())); public List MissingFacilities() { - return MissingFacilities( Research ); + return MissingFacilities(Research); } /// @@ -381,29 +379,29 @@ private string GetResearchTooltipString() { // start with the descripton var text = new StringBuilder(); - text.AppendLine( Research.description ); + text.AppendLine(Research.description); text.AppendLine(); - if ( Queue.IsQueued( this ) ) + if (Queue.IsQueued(this)) { - text.AppendLine( "Fluffy.ResearchTree.LClickRemoveFromQueue".Translate() ); + text.AppendLine("Fluffy.ResearchTree.LClickRemoveFromQueue".Translate()); } else { - text.AppendLine( "Fluffy.ResearchTree.LClickReplaceQueue".Translate() ); - text.AppendLine( "Fluffy.ResearchTree.SLClickAddToQueue".Translate() ); + text.AppendLine("Fluffy.ResearchTree.LClickReplaceQueue".Translate()); + text.AppendLine("Fluffy.ResearchTree.SLClickAddToQueue".Translate()); } - if ( DebugSettings.godMode ) text.AppendLine( "Fluffy.ResearchTree.RClickInstaFinish".Translate() ); + if (DebugSettings.godMode) text.AppendLine("Fluffy.ResearchTree.RClickInstaFinish".Translate()); return text.ToString(); } - public void DrawAt( Vector2 pos, Rect visibleRect, bool forceDetailedMode = false ) + public void DrawAt(Vector2 pos, Rect visibleRect, bool forceDetailedMode = false) { - SetRects( pos ); - Draw( visibleRect, forceDetailedMode ); + SetRects(pos); + Draw(visibleRect, forceDetailedMode); SetRects(); } } diff --git a/Source/Graph/Tree.cs b/Source/Graph/Tree.cs index 81465d0..04abcb5 100644 --- a/Source/Graph/Tree.cs +++ b/Source/Graph/Tree.cs @@ -1,13 +1,13 @@ // Tree.cs // Copyright Karel Kroeze, 2020-2020 -//using Multiplayer.API; +using RimWorld; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; -using RimWorld; +using System.Threading.Tasks; using UnityEngine; using Verse; using static FluffyResearchTree.Constants; @@ -16,11 +16,11 @@ namespace FluffyResearchTree { public static class Tree { - public static bool Initialized; - public static IntVec2 Size = IntVec2.Zero; - private static List _nodes; - private static List> _edges; - private static List _relevantTechLevels; + public static bool Initialized; + public static IntVec2 Size = IntVec2.Zero; + private static List _nodes; + private static List> _edges; + private static List _relevantTechLevels; private static Dictionary _techLevelBounds; private static bool _initializing; @@ -31,8 +31,8 @@ public static Dictionary TechLevelBounds { get { - if ( _techLevelBounds == null ) - throw new Exception( "TechLevelBounds called before they are set." ); + if (_techLevelBounds == null) + throw new Exception("TechLevelBounds called before they are set."); return _techLevelBounds; } } @@ -41,14 +41,14 @@ public static List RelevantTechLevels { get { - if ( _relevantTechLevels == null ) - _relevantTechLevels = Enum.GetValues( typeof( TechLevel ) ) + if (_relevantTechLevels == null) + _relevantTechLevels = Enum.GetValues(typeof(TechLevel)) .Cast() - // filter down to relevant tech levels only. + // filter down to relevant tech levels only. .Where( tl => DefDatabase.AllDefsListForReading.Any( rp => rp.techLevel == - tl ) ) + tl)) .ToList(); return _relevantTechLevels; } @@ -58,7 +58,7 @@ public static List Nodes { get { - if ( _nodes == null ) + if (_nodes == null) PopulateNodes(); return _nodes; @@ -69,71 +69,71 @@ public static List> Edges { get { - if ( _edges == null ) - throw new Exception( "Trying to access edges before they are initialized." ); + if (_edges == null) + throw new Exception("Trying to access edges before they are initialized."); return _edges; } } -// [SyncMethod] + // [SyncMethod] public static void Initialize() { - if ( Initialized ) return; + if (Initialized) return; // make sure we only have one initializer running - if ( _initializing ) + if (_initializing) return; _initializing = true; // setup - LongEventHandler.QueueLongEvent( CheckPrerequisites, "Fluffy.ResearchTree.PreparingTree.Setup", false, - null ); - LongEventHandler.QueueLongEvent( CreateEdges, "Fluffy.ResearchTree.PreparingTree.Setup", false, null ); - LongEventHandler.QueueLongEvent( HorizontalPositions, "Fluffy.ResearchTree.PreparingTree.Setup", false, - null ); - LongEventHandler.QueueLongEvent( NormalizeEdges, "Fluffy.ResearchTree.PreparingTree.Setup", false, null ); + LongEventHandler.QueueLongEvent(CheckPrerequisites, "Fluffy.ResearchTree.PreparingTree.Setup", false, + null); + LongEventHandler.QueueLongEvent(CreateEdges, "Fluffy.ResearchTree.PreparingTree.Setup", false, null); + LongEventHandler.QueueLongEvent(HorizontalPositions, "Fluffy.ResearchTree.PreparingTree.Setup", false, + null); + LongEventHandler.QueueLongEvent(NormalizeEdges, "Fluffy.ResearchTree.PreparingTree.Setup", false, null); #if DEBUG - LongEventHandler.QueueLongEvent( DebugStatus, "Fluffy.ResearchTree.PreparingTree.Setup", false, null ); + LongEventHandler.QueueLongEvent(DebugStatus, "Fluffy.ResearchTree.PreparingTree.Setup", false, null); #endif // crossing reduction - LongEventHandler.QueueLongEvent( Collapse, "Fluffy.ResearchTree.PreparingTree.CrossingReduction", false, - null ); - LongEventHandler.QueueLongEvent( MinimizeCrossings, "Fluffy.ResearchTree.PreparingTree.CrossingReduction", - false, null ); + LongEventHandler.QueueLongEvent(Collapse, "Fluffy.ResearchTree.PreparingTree.CrossingReduction", false, + null); + LongEventHandler.QueueLongEvent(MinimizeCrossings, "Fluffy.ResearchTree.PreparingTree.CrossingReduction", + false, null); #if DEBUG - LongEventHandler.QueueLongEvent( DebugStatus, "Fluffy.ResearchTree.PreparingTree.CrossingReduction", false, - null ); + LongEventHandler.QueueLongEvent(DebugStatus, "Fluffy.ResearchTree.PreparingTree.CrossingReduction", false, + null); #endif // layout - LongEventHandler.QueueLongEvent( MinimizeEdgeLength, "Fluffy.ResearchTree.PreparingTree.Layout", false, - null ); - LongEventHandler.QueueLongEvent( RemoveEmptyRows, "Fluffy.ResearchTree.PreparingTree.Layout", false, null ); + LongEventHandler.QueueLongEvent(MinimizeEdgeLength, "Fluffy.ResearchTree.PreparingTree.Layout", false, + null); + LongEventHandler.QueueLongEvent(RemoveEmptyRows, "Fluffy.ResearchTree.PreparingTree.Layout", false, null); #if DEBUG - LongEventHandler.QueueLongEvent( DebugStatus, "Fluffy.ResearchTree.PreparingTree.Layout", false, null ); + LongEventHandler.QueueLongEvent(DebugStatus, "Fluffy.ResearchTree.PreparingTree.Layout", false, null); #endif // done! - LongEventHandler.QueueLongEvent( () => { Initialized = true; }, "Fluffy.ResearchTree.PreparingTree.Layout", - false, null ); + LongEventHandler.QueueLongEvent(() => { Initialized = true; }, "Fluffy.ResearchTree.PreparingTree.Layout", + false, null); // tell research tab we're ready - LongEventHandler.QueueLongEvent( MainTabWindow_ResearchTree.Instance.Notify_TreeInitialized, - "Fluffy.ResearchTree.RestoreQueue", false, null ); + LongEventHandler.QueueLongEvent(MainTabWindow_ResearchTree.Instance.Notify_TreeInitialized, + "Fluffy.ResearchTree.RestoreQueue", false, null); } private static void RemoveEmptyRows() { - Log.Debug( "Removing empty rows" ); + Log.Debug("Removing empty rows"); Profiler.Start(); var y = 1; - while ( y <= Size.z ) + while (y <= Size.z) { - var row = Row( y ); - if ( row.NullOrEmpty() ) - foreach ( var node in Nodes.Where( n => n.Y > y ) ) + var row = Row(y); + if (row.NullOrEmpty()) + foreach (var node in Nodes.Where(n => n.Y > y)) node.Y--; else y++; @@ -144,43 +144,43 @@ private static void RemoveEmptyRows() private static void MinimizeEdgeLength() { - Log.Debug( "Minimize edge length." ); + Log.Debug("Minimize edge length."); Profiler.Start(); // move and/or swap nodes to reduce the total edge length // perform sweeps of adjacent node reorderings - var progress = false; + var progress = false; int iteration = 0, burnout = 2, max_iterations = 50; - while ( ( !progress || burnout > 0 ) && iteration < max_iterations ) + while ((!progress || burnout > 0) && iteration < max_iterations) { - progress = EdgeLengthSweep_Local( iteration++ ); - if ( !progress ) + progress = EdgeLengthSweep_Local(iteration++); + if (!progress) burnout--; } // sweep until we had no progress 2 times, then keep sweeping until we had progress iteration = 0; - burnout = 2; - while ( burnout > 0 && iteration < max_iterations ) + burnout = 2; + while (burnout > 0 && iteration < max_iterations) { - progress = EdgeLengthSweep_Global( iteration++ ); - if ( !progress ) + progress = EdgeLengthSweep_Global(iteration++); + if (!progress) burnout--; } Profiler.End(); } - private static bool EdgeLengthSweep_Global( int iteration ) + private static bool EdgeLengthSweep_Global(int iteration) { - Profiler.Start( "iteration" + iteration ); + Profiler.Start("iteration" + iteration); // calculate edge length before sweep var before = EdgeLength(); // do left/right sweep, align with left/right nodes for 4 different iterations. //if (iteration % 2 == 0) - for ( var l = 2; l <= Size.x; l++ ) - EdgeLengthSweep_Global_Layer( l, true ); + for (var l = 2; l <= Size.x; l++) + EdgeLengthSweep_Global_Layer(l, true); //else // for (var l = 1; l < Size.x; l++) // EdgeLengthSweep_Global_Layer(l, false); @@ -189,90 +189,91 @@ private static bool EdgeLengthSweep_Global( int iteration ) var after = EdgeLength(); // return progress - Log.Debug( $"EdgeLengthSweep_Global, iteration {iteration}: {before} -> {after}" ); + Log.Debug($"EdgeLengthSweep_Global, iteration {iteration}: {before} -> {after}"); Profiler.End(); return after < before; } - private static bool EdgeLengthSweep_Local( int iteration ) + private static bool EdgeLengthSweep_Local(int iteration) { - Profiler.Start( "iteration" + iteration ); + Profiler.Start("iteration" + iteration); // calculate edge length before sweep var before = EdgeLength(); // do left/right sweep, align with left/right nodes for 4 different iterations. - if ( iteration % 2 == 0 ) - for ( var l = 2; l <= Size.x; l++ ) - EdgeLengthSweep_Local_Layer( l, true ); + if (iteration % 2 == 0) + for (var l = 2; l <= Size.x; l++) + EdgeLengthSweep_Local_Layer(l, true); else - for ( var l = Size.x - 1; l >= 0; l-- ) - EdgeLengthSweep_Local_Layer( l, false ); + for (var l = Size.x - 1; l >= 0; l--) + EdgeLengthSweep_Local_Layer(l, false); // calculate edge length after sweep var after = EdgeLength(); // return progress - Log.Debug( $"EdgeLengthSweep_Local, iteration {iteration}: {before} -> {after}" ); + Log.Debug($"EdgeLengthSweep_Local, iteration {iteration}: {before} -> {after}"); Profiler.End(); return after < before; } - private static void EdgeLengthSweep_Global_Layer( int l, bool @in ) + private static void EdgeLengthSweep_Global_Layer(int l, bool @in) { // The objective here is to; // (1) move and/or swap nodes to reduce total edge length // (2) not increase the number of crossings - var length = EdgeLength( l, @in ); - var crossings = Crossings( l ); - if ( Math.Abs( length ) < Epsilon ) + var length = EdgeLength(l, @in); + var crossings = Crossings(l); + if (Math.Abs(length) < Epsilon) return; - var layer = Layer( l, true ); - foreach ( var node in layer ) + var layer = Layer(l, true); + //Couldn't parallelize + foreach (var node in layer) { // we only need to loop over positions that might be better for this node. // min = minimum of current position, minimum of any connected nodes current position var neighbours = node.Nodes; - if ( !neighbours.Any() ) + if (!neighbours.Any()) continue; - var min = Mathf.Min( node.Y, neighbours.Min( n => n.Y ) ); - var max = Mathf.Max( node.Y, neighbours.Max( n => n.Y ) ); - if ( min == max && min == node.Y ) + var min = Mathf.Min(node.Y, neighbours.Min(n => n.Y)); + var max = Mathf.Max(node.Y, neighbours.Max(n => n.Y)); + if (min == max && min == node.Y) continue; - for ( var y = min; y <= max; y++ ) + for (var y = min; y <= max; y++) { - if ( y == node.Y ) + if (y == node.Y) continue; // is this spot occupied? - var otherNode = NodeAt( l, y ); + var otherNode = NodeAt(l, y); // occupied, try swapping - if ( otherNode != null ) + if (otherNode != null) { - Swap( node, otherNode ); - var candidateCrossings = Crossings( l ); - if ( candidateCrossings > crossings ) + Swap(node, otherNode); + var candidateCrossings = Crossings(l); + if (candidateCrossings > crossings) { // abort - Swap( otherNode, node ); + Swap(otherNode, node); } else { - var candidateLength = EdgeLength( l, @in ); - if ( length - candidateLength < Epsilon ) + var candidateLength = EdgeLength(l, @in); + if (length - candidateLength < Epsilon) { // abort - Swap( otherNode, node ); + Swap(otherNode, node); } else { - Log.Trace( "\tSwapping {0} and {1}: {2} -> {3}", node, otherNode, length, - candidateLength ); + Log.Trace("\tSwapping {0} and {1}: {2} -> {3}", node, otherNode, length, + candidateLength); length = candidateLength; } } @@ -283,24 +284,24 @@ private static void EdgeLengthSweep_Global_Layer( int l, bool @in ) { var oldY = node.Y; node.Y = y; - var candidateCrossings = Crossings( l ); - if ( candidateCrossings > crossings ) + var candidateCrossings = Crossings(l); + if (candidateCrossings > crossings) { // abort node.Y = oldY; } else { - var candidateLength = EdgeLength( l, @in ); - if ( length - candidateLength < Epsilon ) + var candidateLength = EdgeLength(l, @in); + if (length - candidateLength < Epsilon) { // abort node.Y = oldY; } else { - Log.Trace( "\tMoving {0} -> {1}: {2} -> {3}", node, new Vector2( node.X, oldY ), length, - candidateLength ); + Log.Trace("\tMoving {0} -> {1}: {2} -> {3}", node, new Vector2(node.X, oldY), length, + candidateLength); length = candidateLength; } } @@ -310,64 +311,64 @@ private static void EdgeLengthSweep_Global_Layer( int l, bool @in ) } - private static void EdgeLengthSweep_Local_Layer( int l, bool @in ) + private static void EdgeLengthSweep_Local_Layer(int l, bool @in) { // The objective here is to; // (1) move and/or swap nodes to reduce local edge length // (2) not increase the number of crossings - var x = @in ? l - 1 : l + 1; - var crossings = Crossings( x ); + var x = @in ? l - 1 : l + 1; + var crossings = Crossings(x); - var layer = Layer( l, true ); - foreach ( var node in layer ) + var layer = Layer(l, true); + foreach (var node in layer) { - foreach ( var edge in @in ? node.InEdges : node.OutEdges ) + foreach (var edge in @in ? node.InEdges : node.OutEdges) { // current length - var length = edge.Length; + var length = edge.Length; var neighbour = @in ? edge.In : edge.Out; - if ( neighbour.X != x ) - Log.Warning( "{0} is not at layer {1}", neighbour, x ); + if (neighbour.X != x) + Log.Warning("{0} is not at layer {1}", neighbour, x); // we only need to loop over positions that might be better for this node. // min = minimum of current position, node position - var min = Mathf.Min( node.Y, neighbour.Y ); - var max = Mathf.Max( node.Y, neighbour.Y ); + var min = Mathf.Min(node.Y, neighbour.Y); + var max = Mathf.Max(node.Y, neighbour.Y); // already at only possible position - if ( min == max && min == node.Y ) + if (min == max && min == node.Y) continue; - for ( var y = min; y <= max; y++ ) + for (var y = min; y <= max; y++) { - if ( y == neighbour.Y ) + if (y == neighbour.Y) continue; // is this spot occupied? - var otherNode = NodeAt( x, y ); + var otherNode = NodeAt(x, y); // occupied, try swapping - if ( otherNode != null ) + if (otherNode != null) { - Swap( neighbour, otherNode ); - var candidateCrossings = Crossings( x ); - if ( candidateCrossings > crossings ) + Swap(neighbour, otherNode); + var candidateCrossings = Crossings(x); + if (candidateCrossings > crossings) { // abort - Swap( otherNode, neighbour ); + Swap(otherNode, neighbour); } else { var candidateLength = edge.Length; - if ( length - candidateLength < Epsilon ) + if (length - candidateLength < Epsilon) { // abort - Swap( otherNode, neighbour ); + Swap(otherNode, neighbour); } else { - Log.Trace( "\tSwapping {0} and {1}: {2} -> {3}", neighbour, otherNode, length, - candidateLength ); + Log.Trace("\tSwapping {0} and {1}: {2} -> {3}", neighbour, otherNode, length, + candidateLength); length = candidateLength; } } @@ -378,8 +379,8 @@ private static void EdgeLengthSweep_Local_Layer( int l, bool @in ) { var oldY = neighbour.Y; neighbour.Y = y; - var candidateCrossings = Crossings( x ); - if ( candidateCrossings > crossings ) + var candidateCrossings = Crossings(x); + if (candidateCrossings > crossings) { // abort neighbour.Y = oldY; @@ -387,15 +388,15 @@ private static void EdgeLengthSweep_Local_Layer( int l, bool @in ) else { var candidateLength = edge.Length; - if ( length - candidateLength < Epsilon ) + if (length - candidateLength < Epsilon) { // abort neighbour.Y = oldY; } else { - Log.Trace( "\tMoving {0} -> {1}: {2} -> {3}", neighbour, - new Vector2( neighbour.X, oldY ), length, candidateLength ); + Log.Trace("\tMoving {0} -> {1}: {2} -> {3}", neighbour, + new Vector2(neighbour.X, oldY), length, candidateLength); length = candidateLength; } } @@ -408,46 +409,46 @@ private static void EdgeLengthSweep_Local_Layer( int l, bool @in ) public static void HorizontalPositions() { // get list of techlevels - var techlevels = RelevantTechLevels; + var techlevels = RelevantTechLevels; bool anyChange; - var iteration = 1; - var maxIterations = 50; + var iteration = 1; + var maxIterations = 50; - Log.Debug( "Assigning horizontal positions." ); + Log.Debug("Assigning horizontal positions."); Profiler.Start(); // assign horizontal positions based on tech levels and prerequisites do { - Profiler.Start( "iteration " + iteration ); + Profiler.Start("iteration " + iteration); var min = 1; anyChange = false; - foreach ( var techlevel in techlevels ) + foreach (var techlevel in techlevels) { // enforce minimum x position based on techlevels - var nodes = Nodes.OfType().Where( n => n.Research.techLevel == techlevel ); - if ( !nodes.Any() ) + var nodes = Nodes.OfType().Where(n => n.Research.techLevel == techlevel); + if (!nodes.Any()) continue; - foreach ( var node in nodes ) - anyChange = node.SetDepth( min ) || anyChange; + foreach (var node in nodes) + anyChange = node.SetDepth(min) || anyChange; - min = nodes.Max( n => n.X ) + 1; + min = nodes.Max(n => n.X) + 1; - Log.Trace( "\t{0}, change: {1}", techlevel, anyChange ); + Log.Trace("\t{0}, change: {1}", techlevel, anyChange); } Profiler.End(); - } while ( anyChange && iteration++ < maxIterations ); + } while (anyChange && iteration++ < maxIterations); // store tech level boundaries _techLevelBounds = new Dictionary(); - foreach ( var techlevel in techlevels ) + foreach (var techlevel in techlevels) { - var nodes = Nodes.OfType().Where( n => n.Research.techLevel == techlevel ); - _techLevelBounds[techlevel] = new IntRange( nodes.Min( n => n.X ) - 1, nodes.Max( n => n.X ) ); + var nodes = Nodes.OfType().Where(n => n.Research.techLevel == techlevel); + _techLevelBounds[techlevel] = new IntRange(nodes.Min(n => n.X) - 1, nodes.Max(n => n.X)); } Profiler.End(); @@ -455,39 +456,42 @@ public static void HorizontalPositions() private static void NormalizeEdges() { - Log.Debug( "Normalizing edges." ); + Log.Debug("Normalizing edges."); Profiler.Start(); - foreach ( var edge in new List>( Edges.Where( e => e.Span > 1 ) ) ) + foreach (var edge in new List>(Edges.Where(e => e.Span > 1))) { - Log.Trace( "\tCreating dummy chain for {0}", edge ); + Log.Trace("\tCreating dummy chain for {0}", edge); // remove and decouple long edge - Edges.Remove( edge ); - edge.In.OutEdges.Remove( edge ); - edge.Out.InEdges.Remove( edge ); - var cur = edge.In; - var yOffset = ( edge.Out.Yf - edge.In.Yf ) / edge.Span; + Edges.Remove(edge); + edge.In.OutEdges.Remove(edge); + edge.Out.InEdges.Remove(edge); + var cur = edge.In; + var yOffset = (edge.Out.Yf - edge.In.Yf) / edge.Span; // create and hook up dummy chain - for ( var x = edge.In.X + 1; x < edge.Out.X; x++ ) + int x = edge.In.X; + Parallel.For(x + 1, Convert.ToInt32(x < edge.Out.X), i => { - var dummy = new DummyNode(); - dummy.X = x; - dummy.Yf = edge.In.Yf + yOffset * ( x - edge.In.X ); - var dummyEdge = new Edge( cur, dummy ); - cur.OutEdges.Add( dummyEdge ); - dummy.InEdges.Add( dummyEdge ); - _nodes.Add( dummy ); - Edges.Add( dummyEdge ); + var dummy = new DummyNode + { + X = x, + Yf = edge.In.Yf + yOffset * (x - edge.In.X) + }; + var dummyEdge = new Edge(cur, dummy); + cur.OutEdges.Add(dummyEdge); + dummy.InEdges.Add(dummyEdge); + _nodes.Add(dummy); + Edges.Add(dummyEdge); cur = dummy; - Log.Trace( "\t\tCreated dummy {0}", dummy ); - } + Log.Trace("\t\tCreated dummy {0}", dummy); + }); // hook up final dummy to out node - var finalEdge = new Edge( cur, edge.Out ); - cur.OutEdges.Add( finalEdge ); - edge.Out.InEdges.Add( finalEdge ); - Edges.Add( finalEdge ); + var finalEdge = new Edge(cur, edge.Out); + cur.OutEdges.Add(finalEdge); + edge.Out.InEdges.Add(finalEdge); + Edges.Add(finalEdge); } Profiler.End(); @@ -495,25 +499,25 @@ private static void NormalizeEdges() private static void CreateEdges() { - Log.Debug( "Creating edges." ); + Log.Debug("Creating edges."); Profiler.Start(); // create links between nodes - if ( _edges.NullOrEmpty() ) _edges = new List>(); + if (_edges.NullOrEmpty()) _edges = new List>(); - foreach ( var node in Nodes.OfType() ) + foreach (var node in Nodes.OfType()) { - if ( node.Research.prerequisites.NullOrEmpty() ) + if (node.Research.prerequisites.NullOrEmpty()) continue; - foreach ( var prerequisite in node.Research.prerequisites ) + foreach (var prerequisite in node.Research.prerequisites) { ResearchNode prerequisiteNode = prerequisite; - if ( prerequisiteNode == null ) + if (prerequisiteNode == null) continue; - var edge = new Edge( prerequisiteNode, node ); - Edges.Add( edge ); - node.InEdges.Add( edge ); - prerequisiteNode.OutEdges.Add( edge ); - Log.Trace( "\tCreated edge {0}", edge ); + var edge = new Edge(prerequisiteNode, node); + Edges.Add(edge); + node.InEdges.Add(edge); + prerequisiteNode.OutEdges.Add(edge); + Log.Trace("\tCreated edge {0}", edge); } } @@ -523,44 +527,44 @@ private static void CreateEdges() private static void CheckPrerequisites() { // check prerequisites - Log.Debug( "Checking prerequisites." ); + Log.Debug("Checking prerequisites."); Profiler.Start(); - var nodes = new Queue( Nodes.OfType() ); + var nodes = new Queue(Nodes.OfType()); // remove redundant prerequisites - while ( nodes.Count > 0 ) + while (nodes.Count > 0) { var node = nodes.Dequeue(); - if ( node.Research.prerequisites.NullOrEmpty() ) + if (node.Research.prerequisites.NullOrEmpty()) continue; - var ancestors = node.Research.prerequisites?.SelectMany( r => r.Ancestors() ).ToList(); - var redundant = ancestors.Intersect( node.Research.prerequisites ); - if ( redundant.Any() ) + var ancestors = node.Research.prerequisites?.SelectMany(r => r.Ancestors()).ToList(); + var redundant = ancestors.Intersect(node.Research.prerequisites); + if (redundant.Any()) { - Log.Warning( "\tredundant prerequisites for {0}: {1}", node.Research.LabelCap, - string.Join( ", ", redundant.Select( r => r.LabelCap ).ToArray() ) ); - foreach ( var redundantPrerequisite in redundant ) - node.Research.prerequisites.Remove( redundantPrerequisite ); + Log.Warning("\tredundant prerequisites for {0}: {1}", node.Research.LabelCap, + string.Join(", ", redundant.Select(r => r.LabelCap).ToArray())); + foreach (var redundantPrerequisite in redundant) + node.Research.prerequisites.Remove(redundantPrerequisite); } } // fix bad techlevels - nodes = new Queue( Nodes.OfType() ); - while ( nodes.Count > 0 ) + nodes = new Queue(Nodes.OfType()); + while (nodes.Count > 0) { var node = nodes.Dequeue(); - if ( !node.Research.prerequisites.NullOrEmpty() ) + if (!node.Research.prerequisites.NullOrEmpty()) // warn and fix badly configured techlevels - if ( node.Research.prerequisites.Any( r => r.techLevel > node.Research.techLevel ) ) + if (node.Research.prerequisites.Any(r => r.techLevel > node.Research.techLevel)) { - Log.Warning( "\t{0} has a lower techlevel than (one of) it's prerequisites", - node.Research.defName ); - node.Research.techLevel = node.Research.prerequisites.Max( r => r.techLevel ); + Log.Warning("\t{0} has a lower techlevel than (one of) it's prerequisites", + node.Research.defName); + node.Research.techLevel = node.Research.prerequisites.Max(r => r.techLevel); // re-enqeue all descendants - foreach ( var descendant in node.Descendants.OfType() ) - nodes.Enqueue( descendant ); + foreach (var descendant in node.Descendants.OfType()) + nodes.Enqueue(descendant); } } @@ -569,198 +573,205 @@ private static void CheckPrerequisites() private static void PopulateNodes() { - Log.Debug( "Populating nodes." ); + Log.Debug("Populating nodes."); Profiler.Start(); var projects = DefDatabase.AllDefsListForReading; // find hidden nodes (nodes that have themselves as a prerequisite) - var hidden = projects.Where( p => p.prerequisites?.Contains( p ) ?? false ); + var hidden = projects.Where(p => p.prerequisites?.Contains(p) ?? false); // find locked nodes (nodes that have a hidden node as a prerequisite) - var locked = projects.Where( p => p.Ancestors().Intersect( hidden ).Any() ); + var locked = projects.Where(p => p.Ancestors().Intersect(hidden).Any()); // populate all nodes - _nodes = new List( DefDatabase.AllDefsListForReading - .Except( hidden ) - .Except( locked ) - .Select( def => new ResearchNode( def ) as Node ) ); - Log.Debug( "\t{0} nodes", _nodes.Count ); + _nodes = new List(DefDatabase.AllDefsListForReading + .Except(hidden) + .Except(locked) + .Select(def => new ResearchNode(def) as Node)); + Log.Debug("\t{0} nodes", _nodes.Count); Profiler.End(); } private static void Collapse() { - Log.Debug( "Collapsing nodes." ); + Log.Debug("Collapsing nodes."); Profiler.Start(); var pre = Size; - for ( var l = 1; l <= Size.x; l++ ) + for (var l = 1; l <= Size.x; l++) { - var nodes = Layer( l, true ); - var Y = 1; - foreach ( var node in nodes ) + var nodes = Layer(l, true); + var Y = 1; + foreach (var node in nodes) node.Y = Y++; } - Log.Debug( "{0} -> {1}", pre, Size ); + Log.Debug("{0} -> {1}", pre, Size); Profiler.End(); } - [Conditional( "DEBUG" )] + [Conditional("DEBUG")] internal static void DebugDraw() { - foreach ( var v in Nodes ) + foreach (var v in Nodes) { - foreach ( var w in v.OutNodes ) Widgets.DrawLine( v.Right, w.Left, Color.white, 1 ); + foreach (var w in v.OutNodes) Widgets.DrawLine(v.Right, w.Left, Color.white, 1); } } - public static void Draw( Rect visibleRect ) + public static void Draw(Rect visibleRect) { - Profiler.Start( "Tree.Draw" ); - Profiler.Start( "techlevels" ); - foreach ( var techlevel in RelevantTechLevels ) - DrawTechLevel( techlevel, visibleRect ); + Profiler.Start("Tree.Draw"); + Profiler.Start("techlevels"); + foreach (var techlevel in RelevantTechLevels) + DrawTechLevel(techlevel, visibleRect); Profiler.End(); - Profiler.Start( "edges" ); - foreach ( var edge in Edges.OrderBy( e => e.DrawOrder ) ) - edge.Draw( visibleRect ); + Profiler.Start("edges"); + foreach (var edge in Edges.OrderBy(e => e.DrawOrder)) + edge.Draw(visibleRect); Profiler.End(); - Profiler.Start( "nodes" ); - foreach ( var node in Nodes ) - node.Draw( visibleRect ); + Profiler.Start("nodes"); + foreach (var node in Nodes) + node.Draw(visibleRect); Profiler.End(); } - public static void DrawTechLevel( TechLevel techlevel, Rect visibleRect ) + public static void DrawTechLevel(TechLevel techlevel, Rect visibleRect) { // determine positions - var xMin = ( NodeSize.x + NodeMargins.x ) * TechLevelBounds[techlevel].min - NodeMargins.x / 2f; - var xMax = ( NodeSize.x + NodeMargins.x ) * TechLevelBounds[techlevel].max - NodeMargins.x / 2f; + var xMin = (NodeSize.x + NodeMargins.x) * TechLevelBounds[techlevel].min - NodeMargins.x / 2f; + var xMax = (NodeSize.x + NodeMargins.x) * TechLevelBounds[techlevel].max - NodeMargins.x / 2f; - GUI.color = Assets.TechLevelColor; + GUI.color = Assets.TechLevelColor; Text.Anchor = TextAnchor.MiddleCenter; // lower bound - if ( TechLevelBounds[techlevel].min > 0 && xMin > visibleRect.xMin && xMin < visibleRect.xMax ) + if (TechLevelBounds[techlevel].min > 0 && xMin > visibleRect.xMin && xMin < visibleRect.xMax) { // line - Widgets.DrawLine( new Vector2( xMin, visibleRect.yMin ), new Vector2( xMin, visibleRect.yMax ), - Assets.TechLevelColor, 1f ); + Widgets.DrawLine(new Vector2(xMin, visibleRect.yMin), new Vector2(xMin, visibleRect.yMax), + Assets.TechLevelColor, 1f); // label var labelRect = new Rect( xMin + TechLevelLabelSize.y / 2f - TechLevelLabelSize.x / 2f, - visibleRect.center.y - TechLevelLabelSize.y / 2f, + visibleRect.center.y - TechLevelLabelSize.y / 2f, TechLevelLabelSize.x, - TechLevelLabelSize.y ); + TechLevelLabelSize.y); - VerticalLabel( labelRect, techlevel.ToStringHuman() ); + VerticalLabel(labelRect, techlevel.ToStringHuman()); } // upper bound - if ( TechLevelBounds[techlevel].max < Size.x && xMax > visibleRect.xMin && xMax < visibleRect.xMax ) + if (TechLevelBounds[techlevel].max < Size.x && xMax > visibleRect.xMin && xMax < visibleRect.xMax) { // label var labelRect = new Rect( xMax - TechLevelLabelSize.y / 2f - TechLevelLabelSize.x / 2f, - visibleRect.center.y - TechLevelLabelSize.y / 2f, + visibleRect.center.y - TechLevelLabelSize.y / 2f, TechLevelLabelSize.x, - TechLevelLabelSize.y ); + TechLevelLabelSize.y); - VerticalLabel( labelRect, techlevel.ToStringHuman() ); + VerticalLabel(labelRect, techlevel.ToStringHuman()); } - GUI.color = Color.white; + GUI.color = Color.white; Text.Anchor = TextAnchor.UpperLeft; } - private static void VerticalLabel( Rect rect, string text ) + private static void VerticalLabel(Rect rect, string text) { // store the scaling matrix var matrix = GUI.matrix; // rotate and then apply the scaling GUI.matrix = Matrix4x4.identity; - GUIUtility.RotateAroundPivot( -90f, rect.center ); + GUIUtility.RotateAroundPivot(-90f, rect.center); GUI.matrix = matrix * GUI.matrix; - Widgets.Label( rect, text ); + Widgets.Label(rect, text); // restore the original scaling matrix GUI.matrix = matrix; } - private static Node NodeAt( int X, int Y ) + private static Node NodeAt(int X, int Y) { - return Nodes.FirstOrDefault( n => n.X == X && n.Y == Y ); + return Nodes.FirstOrDefault(n => n.X == X && n.Y == Y); } public static void MinimizeCrossings() { // initialize each layer by putting nodes with the most (recursive!) children on bottom - Log.Debug( "Minimize crossings." ); + //shown in-game as "reducing crossings" + Log.Debug("Minimize crossings."); Profiler.Start(); - for ( var X = 1; X <= Size.x; X++ ) + int X = Size.x; + var options = new ParallelOptions() { MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 0.75) * 2.0)) }; + var nodes = Layer(X).OrderBy(n => n.Descendants.Count).ToList(); + int start = 0; + int end = nodes.Count; + Parallel.For(1, X, options, i => { - var nodes = Layer( X ).OrderBy( n => n.Descendants.Count ).ToList(); - for ( var i = 0; i < nodes.Count; i++ ) - nodes[i].Y = i + 1; - } + for (var z = start; z < end; z++) + { + nodes[z].Y = z + 1; + } + }); // up-down sweeps of mean reordering - var progress = false; + var progress = false; int iteration = 0, burnout = 2, max_iterations = 50; - while ( ( !progress || burnout > 0 ) && iteration < max_iterations ) + while ((!progress || burnout > 0) && iteration < max_iterations) { - progress = BarymetricSweep( iteration++ ); - if ( !progress ) + progress = BarymetricSweep(iteration++); + if (!progress) burnout--; } // greedy sweep for local optima iteration = 0; - burnout = 2; - while ( burnout > 0 && iteration < max_iterations ) + burnout = 2; + while (burnout > 0 && iteration < max_iterations) { - progress = GreedySweep( iteration++ ); - if ( !progress ) + progress = GreedySweep(iteration++); + if (!progress) burnout--; } Profiler.End(); } - private static bool GreedySweep( int iteration ) + private static bool GreedySweep(int iteration) { - Profiler.Start( "iteration " + iteration ); + Profiler.Start("iteration " + iteration); // count number of crossings before sweep var before = Crossings(); // do up/down sweep on aternating iterations - if ( iteration % 2 == 0 ) - for ( var l = 1; l <= Size.x; l++ ) - GreedySweep_Layer( l ); + if (iteration % 2 == 0) + for (var l = 1; l <= Size.x; l++) + GreedySweep_Layer(l); else - for ( var l = Size.x; l >= 1; l-- ) - GreedySweep_Layer( l ); + for (var l = Size.x; l >= 1; l--) + GreedySweep_Layer(l); // count number of crossings after sweep var after = Crossings(); - Log.Debug( $"GreedySweep: {before} -> {after}" ); + Log.Debug($"GreedySweep: {before} -> {after}"); Profiler.End(); // return progress return after < before; } - private static void GreedySweep_Layer( int l ) + private static void GreedySweep_Layer(int l) { // The objective here is twofold; // 1: Swap nodes to reduce the number of crossings @@ -769,209 +780,200 @@ private static void GreedySweep_Layer( int l ) // // If I'm reasoning this out right, both objectives should be served by // minimizing the amount of crossings between each pair of nodes. - var crossings = Crossings( l ); - if ( crossings == 0 ) + var crossings = Crossings(l); + if (crossings == 0) return; - var layer = Layer( l, true ); - for ( var i = 0; i < layer.Count - 1; i++ ) + var layer = Layer(l, true); + for (var i = 0; i < layer.Count - 1; i++) { - for ( var j = i + 1; j < layer.Count; j++ ) + for (var j = i + 1; j < layer.Count; j++) { // swap, then count crossings again. If lower, leave it. If higher, revert. - Swap( layer[i], layer[j] ); - var candidateCrossings = Crossings( l ); - if ( candidateCrossings < crossings ) + Swap(layer[i], layer[j]); + var candidateCrossings = Crossings(l); + if (candidateCrossings < crossings) // update current crossings crossings = candidateCrossings; else // revert change - Swap( layer[j], layer[i] ); + Swap(layer[j], layer[i]); } } } - private static void Swap( Node A, Node B ) + private static void Swap(Node A, Node B) { - if ( A.X != B.X ) - throw new Exception( "Can't swap nodes on different layers" ); + if (A.X != B.X) + throw new Exception("Can't swap nodes on different layers"); // swap Y positions of adjacent nodes - var tmp = A.Y; - A.Y = B.Y; - B.Y = tmp; + (B.Y, A.Y) = (A.Y, B.Y); } - private static bool BarymetricSweep( int iteration ) + private static bool BarymetricSweep(int iteration) { - Profiler.Start( "iteration " + iteration ); + Profiler.Start("iteration " + iteration); // count number of crossings before sweep var before = Crossings(); // do up/down sweep on alternating iterations - if ( iteration % 2 == 0 ) - for ( var i = 2; i <= Size.x; i++ ) - BarymetricSweep_Layer( i, true ); + if (iteration % 2 == 0) + for (var i = 2; i <= Size.x; i++) + BarymetricSweep_Layer(i, true); else - for ( var i = Size.x - 1; i > 0; i-- ) - BarymetricSweep_Layer( i, false ); + for (var i = Size.x - 1; i > 0; i--) + BarymetricSweep_Layer(i, false); // count number of crossings after sweep var after = Crossings(); // did we make progress? please? Log.Debug( - $"BarymetricSweep {iteration} ({( iteration % 2 == 0 ? "left" : "right" )}): {before} -> {after}" ); + $"BarymetricSweep {iteration} ({(iteration % 2 == 0 ? "left" : "right")}): {before} -> {after}"); Profiler.End(); return after < before; } - private static void BarymetricSweep_Layer( int layer, bool left ) + private static void BarymetricSweep_Layer(int layer, bool left) { - var means = Layer( layer ) - .ToDictionary( n => n, n => GetBarycentre( n, left ? n.InNodes : n.OutNodes ) ) - .OrderBy( n => n.Value ); + var means = Layer(layer) + .ToDictionary(n => n, n => GetBarycentre(n, left ? n.InNodes : n.OutNodes)) + .OrderBy(n => n.Value); // create groups of nodes at similar means - var cur = float.MinValue; + var cur = float.MinValue; var groups = new Dictionary>(); - foreach ( var mean in means ) + foreach (var mean in means) { - if ( Math.Abs( mean.Value - cur ) > Epsilon ) + if (Math.Abs(mean.Value - cur) > Epsilon) { - cur = mean.Value; + cur = mean.Value; groups[cur] = new List(); } - groups[cur].Add( mean.Key ); + groups[cur].Add(mean.Key); } // position nodes as close to their desired mean as possible var Y = 1; - foreach ( var group in groups ) + foreach (var group in groups) { var mean = group.Key; - var N = group.Value.Count; - Y = (int) Mathf.Max( Y, mean - ( N - 1 ) / 2 ); + var N = group.Value.Count; + Y = (int)Mathf.Max(Y, mean - (N - 1) / 2); - foreach ( var node in group.Value ) + foreach (var node in group.Value) node.Y = Y++; } } - private static float GetBarycentre( Node node, List neighbours ) + private static float GetBarycentre(Node node, List neighbours) { - if ( neighbours.NullOrEmpty() ) + if (neighbours.NullOrEmpty()) return node.Yf; - return neighbours.Sum( n => n.Yf ) / neighbours.Count; + return neighbours.Sum(n => n.Yf) / neighbours.Count; } private static int Crossings() { - var crossings = 0; - for ( var layer = 1; layer < Size.x; layer++ ) crossings += Crossings( layer, true ); + var crossings = 0; + for (var layer = 1; layer < Size.x; layer++) crossings += Crossings(layer, true); return crossings; } private static float EdgeLength() { - var length = 0f; - for ( var layer = 1; layer < Size.x; layer++ ) length += EdgeLength( layer, true ); + var length = 0f; + for (var layer = 1; layer < Size.x; layer++) length += EdgeLength(layer, true); return length; } - private static int Crossings( int layer ) + private static int Crossings(int layer) { - if ( layer == 0 ) - return Crossings( layer, false ); - if ( layer == Size.x ) - return Crossings( layer, true ); - return Crossings( layer, true ) + Crossings( layer, false ); + if (layer == 0) + return Crossings(layer, false); + if (layer == Size.x) + return Crossings(layer, true); + return Crossings(layer, true) + Crossings(layer, false); } - private static float EdgeLength( int layer ) - { - if ( layer == 0 ) - return EdgeLength( layer, false ); - if ( layer == Size.x ) - return EdgeLength( layer, true ); - return EdgeLength( layer, true ) * - EdgeLength( layer, false ); // multply to favor moving nodes closer to one endpoint - } - - private static int Crossings( int layer, bool @in ) + private static int Crossings(int layer, bool @in) { // get in/out edges for layer - var edges = Layer( layer ) - .SelectMany( n => @in ? n.InEdges : n.OutEdges ) - .OrderBy( e => e.In.Y ) - .ThenBy( e => e.Out.Y ) + var edges = Layer(layer) + .SelectMany(n => @in ? n.InEdges : n.OutEdges) + .OrderBy(e => e.In.Y) + .ThenBy(e => e.Out.Y) .ToList(); - if ( edges.Count < 2 ) + if (edges.Count < 2) return 0; // count number of inversions var inversions = 0; - for ( var i = 0; i < edges.Count - 1; i++ ) + int X = edges.Count; + var options = new ParallelOptions() { MaxDegreeOfParallelism = Convert.ToInt32(Math.Ceiling((Environment.ProcessorCount * 0.75) * 2.0)) }; + Parallel.For(0, X - 1, options, i => { - for ( var j = i + 1; j < edges.Count; j++ ) - if ( edges[j].Out.Y < edges[i].Out.Y ) + for (var j = i + 1; j < edges.Count; j++) + if (edges[j].Out.Y < edges[i].Out.Y) inversions++; - } + }); return inversions; } - private static float EdgeLength( int layer, bool @in ) + private static float EdgeLength(int layer, bool @in) { // get in/out edges for layer - var edges = Layer( layer ) - .SelectMany( n => @in ? n.InEdges : n.OutEdges ) - .OrderBy( e => e.In.Y ) - .ThenBy( e => e.Out.Y ) + var edges = Layer(layer) + .SelectMany(n => @in ? n.InEdges : n.OutEdges) + .OrderBy(e => e.In.Y) + .ThenBy(e => e.Out.Y) .ToList(); - if ( edges.NullOrEmpty() ) + if (edges.NullOrEmpty()) return 0f; - return edges.Sum( e => e.Length ) * ( @in ? 2 : 1 ); + return edges.Sum(e => e.Length) * (@in ? 2 : 1); } - public static List Layer( int depth, bool ordered = false ) + public static List Layer(int depth, bool ordered = false) { - if ( ordered && OrderDirty ) + if (ordered && OrderDirty) { - _nodes = Nodes.OrderBy( n => n.X ).ThenBy( n => n.Y ).ToList(); + _nodes = Nodes.OrderBy(n => n.X).ThenBy(n => n.Y).ToList(); OrderDirty = false; } - return Nodes.Where( n => n.X == depth ).ToList(); + return Nodes.Where(n => n.X == depth).ToList(); } - public static List Row( int Y ) + public static List Row(int Y) { - return Nodes.Where( n => n.Y == Y ).ToList(); + return Nodes.Where(n => n.Y == Y).ToList(); } + //This one is probably unsafe to execute in parallel public new static string ToString() { var text = new StringBuilder(); - for ( var l = 1; l <= Nodes.Max( n => n.X ); l++ ) + for (var l = 1; l <= Nodes.Max(n => n.X); l++) { - text.AppendLine( $"Layer {l}:" ); - var layer = Layer( l, true ); + text.AppendLine($"Layer {l}:"); + var layer = Layer(l, true); - foreach ( var n in layer ) + foreach (var n in layer) { - text.AppendLine( $"\t{n}" ); - text.AppendLine( "\t\tAbove: " + - string.Join( ", ", n.InNodes.Select( a => a.ToString() ).ToArray() ) ); - text.AppendLine( "\t\tBelow: " + - string.Join( ", ", n.OutNodes.Select( b => b.ToString() ).ToArray() ) ); + text.AppendLine($"\t{n}"); + text.AppendLine("\t\tAbove: " + + string.Join(", ", n.InNodes.Select(a => a.ToString()).ToArray())); + text.AppendLine("\t\tBelow: " + + string.Join(", ", n.OutNodes.Select(b => b.ToString()).ToArray())); } } @@ -980,15 +982,15 @@ public static List Row( int Y ) public static void DebugStatus() { - Log.Message( "duplicated positions:\n " + + Log.Message("duplicated positions:\n " + string.Join( "\n", - Nodes.Where( n => Nodes.Any( n2 => n != n2 && n.X == n2.X && n.Y == n2.Y ) ) - .Select( n => n.X + ", " + n.Y + ": " + n.Label ).ToArray() ) ); - Log.Message( "out-of-bounds nodes:\n" + + Nodes.Where(n => Nodes.Any(n2 => n != n2 && n.X == n2.X && n.Y == n2.Y)) + .Select(n => n.X + ", " + n.Y + ": " + n.Label).ToArray())); + Log.Message("out-of-bounds nodes:\n" + string.Join( - "\n", Nodes.Where( n => n.X < 1 || n.Y < 1 ).Select( n => n.ToString() ).ToArray() ) ); - Log.Trace( ToString() ); + "\n", Nodes.Where(n => n.X < 1 || n.Y < 1).Select(n => n.ToString()).ToArray())); + Log.Trace(ToString()); } } } \ No newline at end of file diff --git a/Source/Log.cs b/Source/Log.cs index bd033e1..a481620 100644 --- a/Source/Log.cs +++ b/Source/Log.cs @@ -7,40 +7,40 @@ namespace FluffyResearchTree { public static class Log { - public static void Message( string msg, params object[] args ) + public static void Message(string msg, params object[] args) { - Verse.Log.Message( Format( msg, args ) ); + Verse.Log.Message(Format(msg, args)); } - public static void Warning( string msg, params object[] args ) + public static void Warning(string msg, params object[] args) { - Verse.Log.Warning( Format( msg, args ) ); + Verse.Log.Warning(Format(msg, args)); } - private static string Format( string msg, params object[] args ) + private static string Format(string msg, params object[] args) { - return "ResearchTree :: " + string.Format( msg, args ); + return "ResearchTree :: " + string.Format(msg, args); } - public static void Error( string msg, bool once, params object[] args ) + public static void Error(string msg, bool once, params object[] args) { - var _msg = Format( msg, args ); - if ( once ) - Verse.Log.ErrorOnce( _msg, _msg.GetHashCode() ); + var _msg = Format(msg, args); + if (once) + Verse.Log.ErrorOnce(_msg, _msg.GetHashCode()); else - Verse.Log.Error( _msg ); + Verse.Log.Error(_msg); } - [Conditional( "DEBUG" )] - public static void Debug( string msg, params object[] args ) + [Conditional("DEBUG")] + public static void Debug(string msg, params object[] args) { - Verse.Log.Message( Format( msg, args ) ); + Verse.Log.Message(Format(msg, args)); } - [Conditional( "TRACE" )] - public static void Trace( string msg, params object[] args ) + [Conditional("TRACE")] + public static void Trace(string msg, params object[] args) { - Verse.Log.Message( Format( msg, args ) ); + Verse.Log.Message(Format(msg, args)); } } } \ No newline at end of file diff --git a/Source/MainButtonWorker_ResearchTree.cs b/Source/MainButtonWorker_ResearchTree.cs index a3d53db..eb61f7f 100644 --- a/Source/MainButtonWorker_ResearchTree.cs +++ b/Source/MainButtonWorker_ResearchTree.cs @@ -9,18 +9,18 @@ namespace FluffyResearchTree { public class MainButtonWorker_ResearchTree : MainButtonWorker_ToggleResearchTab { - public override void DoButton( Rect rect ) + public override void DoButton(Rect rect) { - base.DoButton( rect ); + base.DoButton(rect); - if ( Queue.NumQueued > 0 ) + if (Queue.NumQueued > 0) { var queueRect = new Rect( rect.xMax - Constants.SmallQueueLabelSize - Constants.Margin, 0f, Constants.SmallQueueLabelSize, - Constants.SmallQueueLabelSize ).CenteredOnYIn( rect ); - Queue.DrawLabel( queueRect, Color.white, Color.grey, Queue.NumQueued ); + Constants.SmallQueueLabelSize).CenteredOnYIn(rect); + Queue.DrawLabel(queueRect, Color.white, Color.grey, Queue.NumQueued); } } } diff --git a/Source/MainTabWindow_ResearchTree.cs b/Source/MainTabWindow_ResearchTree.cs index 59dff8b..3a5924c 100644 --- a/Source/MainTabWindow_ResearchTree.cs +++ b/Source/MainTabWindow_ResearchTree.cs @@ -1,373 +1,400 @@ -// MainTabWindow_ResearchTree.cs -// Copyright Karel Kroeze, 2020-2020 - -using System.Collections.Generic; -using System.Linq; -using RimWorld; -using UnityEngine; -using Verse; -using static FluffyResearchTree.Constants; - -namespace FluffyResearchTree -{ - public class MainTabWindow_ResearchTree : MainTabWindow - { - internal static Vector2 _scrollPosition = Vector2.zero; - - private static Rect _treeRect; - - private Rect _baseViewRect; - private Rect _baseViewRect_Inner; - - private bool _dragging; - private Vector2 _mousePosition = Vector2.zero; - - private string _query = ""; - private Rect _viewRect; - - private Rect _viewRect_Inner; - private bool _viewRect_InnerDirty = true; - private bool _viewRectDirty = true; - - private float _zoomLevel = 1f; - - public MainTabWindow_ResearchTree() - { - closeOnClickedOutside = false; - Instance = this; - } - - public static MainTabWindow_ResearchTree Instance { get; private set; } - - public float ScaledMargin => Constants.Margin * ZoomLevel / Prefs.UIScale; - - public float ZoomLevel - { - get => _zoomLevel; - set - { - _zoomLevel = Mathf.Clamp( value, 1f, MaxZoomLevel ); - _viewRectDirty = true; - _viewRect_InnerDirty = true; - } - } - - public Rect ViewRect - { - get - { - if ( _viewRectDirty ) - { - _viewRect = new Rect( - _baseViewRect.xMin * ZoomLevel, - _baseViewRect.yMin * ZoomLevel, - _baseViewRect.width * ZoomLevel, - _baseViewRect.height * ZoomLevel - ); - _viewRectDirty = false; - } - - return _viewRect; - } - } - - public Rect ViewRect_Inner - { - get - { - if ( _viewRect_InnerDirty ) - { - _viewRect_Inner = _viewRect.ContractedBy( Margin * ZoomLevel ); - _viewRect_InnerDirty = false; - } - - return _viewRect_Inner; - } - } - - public Rect TreeRect - { - get - { - if ( _treeRect == default ) - { - var width = Tree.Size.x * ( NodeSize.x + NodeMargins.x ); - var height = Tree.Size.z * ( NodeSize.y + NodeMargins.y ); - _treeRect = new Rect( 0f, 0f, width, height ); - } - - return _treeRect; - } - } - - public Rect VisibleRect => - new Rect( - _scrollPosition.x, - _scrollPosition.y, - ViewRect_Inner.width, - ViewRect_Inner.height ); - - internal float MaxZoomLevel - { - get - { - // get the minimum zoom level at which the entire tree fits onto the screen, or a static maximum zoom level. - var fitZoomLevel = Mathf.Max( TreeRect.width / _baseViewRect_Inner.width, - TreeRect.height / _baseViewRect_Inner.height ); - return Mathf.Min( fitZoomLevel, AbsoluteMaxZoomLevel ); - } - } - - public override void PreClose() - { - base.PreClose(); - Log.Debug( "CloseOnClickedOutside: {0}", closeOnClickedOutside ); - Log.Debug( StackTraceUtility.ExtractStackTrace() ); - } - - public void Notify_TreeInitialized() - { - SetRects(); - } - - public override void PreOpen() - { - base.PreOpen(); - SetRects(); - - if ( !Tree.Initialized ) - // initialize tree - Tree.Initialize(); - - // clear node availability caches - ResearchNode.ClearCaches(); - - _dragging = false; - closeOnClickedOutside = false; - } - - private void SetRects() - { - // tree view rects, have to deal with UIScale and ZoomLevel manually. - _baseViewRect = new Rect( - StandardMargin / Prefs.UIScale, - ( TopBarHeight + Constants.Margin + StandardMargin ) / Prefs.UIScale, - ( Screen.width - StandardMargin * 2f ) / Prefs.UIScale, - ( Screen.height - MainButtonDef.ButtonHeight - StandardMargin * 2f - TopBarHeight - Constants.Margin ) / - Prefs.UIScale ); - _baseViewRect_Inner = _baseViewRect.ContractedBy( Constants.Margin / Prefs.UIScale ); - - // windowrect, set to topleft (for some reason vanilla alignment overlaps bottom buttons). - windowRect.x = 0f; - windowRect.y = 0f; - windowRect.width = UI.screenWidth; - windowRect.height = UI.screenHeight - MainButtonDef.ButtonHeight; - } - - public override void DoWindowContents( Rect canvas ) - { - if ( !Tree.Initialized ) - return; - - - // top bar - var topRect = new Rect( - canvas.xMin, - canvas.yMin, - canvas.width, - TopBarHeight ); - DrawTopBar( topRect ); - - ApplyZoomLevel(); - - // draw background - GUI.DrawTexture( ViewRect, Assets.SlightlyDarkBackground ); - - // draw the actual tree - // TODO: stop scrollbars scaling with zoom - _scrollPosition = GUI.BeginScrollView( ViewRect, _scrollPosition, TreeRect ); - GUI.BeginGroup( - new Rect( - ScaledMargin, - ScaledMargin, - TreeRect.width + ScaledMargin * 2f, - TreeRect.height + ScaledMargin * 2f - ) - ); - - Tree.Draw( VisibleRect ); - Queue.DrawLabels( VisibleRect ); - - HandleZoom(); - - GUI.EndGroup(); - GUI.EndScrollView( false ); - - HandleDragging(); - HandleDolly(); - - // reset zoom level - ResetZoomLevel(); - - - // cleanup; - GUI.color = Color.white; - Text.Anchor = TextAnchor.UpperLeft; - } - - private void HandleDolly() - { - var dollySpeed = 10f; - if ( KeyBindingDefOf.MapDolly_Left.IsDown ) - _scrollPosition.x -= dollySpeed; - if ( KeyBindingDefOf.MapDolly_Right.IsDown ) - _scrollPosition.x += dollySpeed; - if ( KeyBindingDefOf.MapDolly_Up.IsDown ) - _scrollPosition.y -= dollySpeed; - if ( KeyBindingDefOf.MapDolly_Down.IsDown ) - _scrollPosition.y += dollySpeed; - } - - - private void HandleZoom() - { - // handle zoom - if ( Event.current.isScrollWheel ) - { - // absolute position of mouse on research tree - var absPos = Event.current.mousePosition; - // Log.Debug( "Absolute position: {0}", absPos ); - - // relative normalized position of mouse on visible tree - var relPos = ( Event.current.mousePosition - _scrollPosition ) / ZoomLevel; - // Log.Debug( "Normalized position: {0}", relPos ); - - // update zoom level - ZoomLevel += Event.current.delta.y * ZoomStep * ZoomLevel; - - // we want to keep the _normalized_ relative position the same as before zooming - _scrollPosition = absPos - relPos * ZoomLevel; - - Event.current.Use(); - } - } - - private void HandleDragging() - { - if ( Event.current.type == EventType.MouseDown ) - { - _dragging = true; - _mousePosition = Event.current.mousePosition; - Event.current.Use(); - } - - if ( Event.current.type == EventType.MouseUp ) - { - _dragging = false; - _mousePosition = Vector2.zero; - } - - if ( Event.current.type == EventType.MouseDrag ) - { - var _currentMousePosition = Event.current.mousePosition; - _scrollPosition += _mousePosition - _currentMousePosition; - _mousePosition = _currentMousePosition; - } - } - - private void ApplyZoomLevel() - { - GUI.EndClip(); // window contents - GUI.EndClip(); // window itself? - GUI.matrix = Matrix4x4.TRS( new Vector3( 0f, 0f, 0f ), Quaternion.identity, - new Vector3( Prefs.UIScale / ZoomLevel, Prefs.UIScale / ZoomLevel, 1f ) ); - } - - private void ResetZoomLevel() - { - // dummies to maintain correct stack size - // TODO; figure out how to get actual clipping rects in ApplyZoomLevel(); - UI.ApplyUIScale(); - GUI.BeginClip( windowRect ); - GUI.BeginClip( new Rect( 0f, 0f, UI.screenWidth, UI.screenHeight ) ); - } - - private void DrawTopBar( Rect canvas ) - { - var searchRect = canvas; - var queueRect = canvas; - searchRect.width = 200f; - queueRect.xMin += 200f + Constants.Margin; - - GUI.DrawTexture( searchRect, Assets.SlightlyDarkBackground ); - GUI.DrawTexture( queueRect, Assets.SlightlyDarkBackground ); - - DrawSearchBar( searchRect.ContractedBy( Constants.Margin ) ); - Queue.DrawQueue( queueRect.ContractedBy( Constants.Margin ), !_dragging ); - } - - private void DrawSearchBar( Rect canvas ) - { - Profiler.Start( "DrawSearchBar" ); - var iconRect = new Rect( - canvas.xMax - Constants.Margin - 16f, - 0f, - 16f, - 16f ) - .CenteredOnYIn( canvas ); - var searchRect = new Rect( - canvas.xMin, - 0f, - canvas.width, - 30f ) - .CenteredOnYIn( canvas ); - - GUI.DrawTexture( iconRect, Assets.Search ); - var query = Widgets.TextField( searchRect, _query ); - - if ( query != _query ) - { - _query = query; - Find.WindowStack.FloatMenu?.Close( false ); - - if ( query.Length > 2 ) - { - // open float menu with search results, if any. - var options = new List(); - - foreach ( var result in Tree.Nodes.OfType() - .Select( n => new {node = n, match = n.Matches( query )} ) - .Where( result => result.match > 0 ) - .OrderBy( result => result.match ) ) - options.Add( new FloatMenuOption( result.node.Label, () => CenterOn( result.node ), - MenuOptionPriority.Default, () => CenterOn( result.node ) ) ); - - if ( !options.Any() ) - options.Add( new FloatMenuOption( "Fluffy.ResearchTree.NoResearchFound".Translate(), null ) ); - - Find.WindowStack.Add( new FloatMenu_Fixed( options, - UI.GUIToScreenPoint( - new Vector2( - searchRect.xMin, searchRect.yMax ) ) ) ); - } - } - - Profiler.End(); - } - - public void CenterOn( Node node ) - { - var position = new Vector2( - ( NodeSize.x + NodeMargins.x ) * ( node.X - .5f ), - ( NodeSize.y + NodeMargins.y ) * ( node.Y - .5f ) ); - - node.Highlighted = true; - - position -= new Vector2( UI.screenWidth, UI.screenHeight ) / 2f; - - position.x = Mathf.Clamp( position.x, 0f, TreeRect.width - ViewRect.width ); - position.y = Mathf.Clamp( position.y, 0f, TreeRect.height - ViewRect.height ); - _scrollPosition = position; - } - } +// MainTabWindow_ResearchTree.cs +// Copyright Karel Kroeze, 2020-2020 + +using RimWorld; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using Verse; +using static FluffyResearchTree.Constants; +using static RimWorld.MainTabWindow_Research; + +namespace FluffyResearchTree +{ + public class MainTabWindow_ResearchTree : MainTabWindow + { + internal static Vector2 _scrollPosition = Vector2.zero; + + private static Rect _treeRect; + + private Rect _baseViewRect; + private Rect _baseViewRect_Inner; + + private bool _dragging; + private Vector2 _mousePosition = Vector2.zero; + + private string _query = ""; + private Rect _viewRect; + + private Rect _viewRect_Inner; + private bool _viewRect_InnerDirty = true; + private bool _viewRectDirty = true; + + private float _zoomLevel = 1f; + + public MainTabWindow_ResearchTree() + { + closeOnClickedOutside = false; + Instance = this; + } + + public static MainTabWindow_ResearchTree Instance { get; private set; } + + public float ScaledMargin => Constants.Margin * ZoomLevel / Prefs.UIScale; + + public float ZoomLevel + { + get => _zoomLevel; + set + { + _zoomLevel = Mathf.Clamp(value, 1f, MaxZoomLevel); + _viewRectDirty = true; + _viewRect_InnerDirty = true; + } + } + + public Rect ViewRect + { + get + { + if (_viewRectDirty) + { + _viewRect = new Rect( + _baseViewRect.xMin * ZoomLevel, + _baseViewRect.yMin * ZoomLevel, + _baseViewRect.width * ZoomLevel, + _baseViewRect.height * ZoomLevel + ); + _viewRectDirty = false; + } + + return _viewRect; + } + } + + public Rect ViewRect_Inner + { + get + { + if (_viewRect_InnerDirty) + { + _viewRect_Inner = _viewRect.ContractedBy(Margin * ZoomLevel); + _viewRect_InnerDirty = false; + } + + return _viewRect_Inner; + } + } + + public Rect TreeRect + { + get + { + if (_treeRect == default) + { + var width = Tree.Size.x * (NodeSize.x + NodeMargins.x); + var height = Tree.Size.z * (NodeSize.y + NodeMargins.y); + _treeRect = new Rect(0f, 0f, width, height); + } + + return _treeRect; + } + } + + public Rect VisibleRect => + new Rect( + _scrollPosition.x, + _scrollPosition.y, + ViewRect_Inner.width, + ViewRect_Inner.height); + + internal float MaxZoomLevel + { + get + { + // get the minimum zoom level at which the entire tree fits onto the screen, or a static maximum zoom level. + var fitZoomLevel = Mathf.Max(TreeRect.width / _baseViewRect_Inner.width, + TreeRect.height / _baseViewRect_Inner.height); + return Mathf.Min(fitZoomLevel, AbsoluteMaxZoomLevel); + } + } + + public void ShowDebugButton() + { + Rect rect = new Rect(0f, 0f, 140f, 0f); + float leftStartAreaHeight = 68f; + float leftViewDebugHeight = 0f; + float num = 0f; + float num2 = 0f; + var selectedProject = Find.ResearchManager.currentProj; + Rect outRect = new Rect(0f, 0f, rect.width, num); + Rect viewRect = new Rect(0f, 0f, outRect.width - 16f, leftViewDebugHeight); + Rect rect11 = new Rect(0f, outRect.yMax + 10f , rect.width, leftStartAreaHeight); + if (Prefs.DevMode && selectedProject != Find.ResearchManager.currentProj && !selectedProject.IsFinished) + { + Text.Font = GameFont.Tiny; + Rect rect13 = new Rect(rect11.x, outRect.yMax, 120f, 30f); + if (Widgets.ButtonText(rect13, "Debug: Finish now")) + { + Find.ResearchManager.currentProj = selectedProject; + Find.ResearchManager.FinishProject(selectedProject); + } + Text.Font = GameFont.Small; + leftViewDebugHeight = rect13.height; + } + } + + public override void PreClose() + { + base.PreClose(); + Log.Debug("CloseOnClickedOutside: {0}", closeOnClickedOutside); + Log.Debug(StackTraceUtility.ExtractStackTrace()); + } + + public void Notify_TreeInitialized() + { + SetRects(); + } + + public override void PreOpen() + { + base.PreOpen(); + SetRects(); + + if (!Tree.Initialized) + // initialize tree + Tree.Initialize(); + + // clear node availability caches + ResearchNode.ClearCaches(); + + _dragging = false; + closeOnClickedOutside = false; + } + + private void SetRects() + { + // tree view rects, have to deal with UIScale and ZoomLevel manually. + _baseViewRect = new Rect( + StandardMargin / Prefs.UIScale, + (TopBarHeight + Constants.Margin + StandardMargin), + (Screen.width - StandardMargin * 2f) / Prefs.UIScale, + ((Screen.height - MainButtonDef.ButtonHeight - StandardMargin * 2) / Prefs.UIScale) - TopBarHeight - Constants.Margin); + + // windowrect, set to topleft (for some reason vanilla alignment overlaps bottom buttons). + windowRect.x = 0f; + windowRect.y = 0f; + windowRect.width = UI.screenWidth; + windowRect.height = UI.screenHeight - MainButtonDef.ButtonHeight; + } + + public override void DoWindowContents(Rect canvas) + { + if (!Tree.Initialized) + return; + + if(Prefs.DevMode) + { + ShowDebugButton(); + } + // top bar + var topRect = new Rect( + canvas.xMin, + canvas.yMin, + canvas.width, + TopBarHeight); + DrawTopBar(topRect); + + ApplyZoomLevel(); + + // draw background + GUI.DrawTexture(ViewRect, Assets.SlightlyDarkBackground); + + // draw the actual tree + // TODO: stop scrollbars scaling with zoom + _scrollPosition = GUI.BeginScrollView(ViewRect, _scrollPosition, TreeRect); + GUI.BeginGroup( + new Rect( + ScaledMargin, + ScaledMargin, + TreeRect.width + ScaledMargin * 2f, + TreeRect.height + ScaledMargin * 2f + ) + ); + + Tree.Draw(VisibleRect); + Queue.DrawLabels(VisibleRect); + + HandleZoom(); + + GUI.EndGroup(); + GUI.EndScrollView(false); + + HandleDragging(); + HandleDolly(); + + // reset zoom level + ResetZoomLevel(); + + + // cleanup; + GUI.color = Color.white; + Text.Anchor = TextAnchor.UpperLeft; + } + + private void HandleDolly() + { + var dollySpeed = 10f; + if (KeyBindingDefOf.MapDolly_Left.IsDown) + _scrollPosition.x -= dollySpeed; + if (KeyBindingDefOf.MapDolly_Right.IsDown) + _scrollPosition.x += dollySpeed; + if (KeyBindingDefOf.MapDolly_Up.IsDown) + _scrollPosition.y -= dollySpeed; + if (KeyBindingDefOf.MapDolly_Down.IsDown) + _scrollPosition.y += dollySpeed; + } + + + private void HandleZoom() + { + // handle zoom + if (Event.current.isScrollWheel) + { + // absolute position of mouse on research tree + var absPos = Event.current.mousePosition; + // Log.Debug( "Absolute position: {0}", absPos ); + + // relative normalized position of mouse on visible tree + var relPos = (Event.current.mousePosition - _scrollPosition) / ZoomLevel; + // Log.Debug( "Normalized position: {0}", relPos ); + + // update zoom level + ZoomLevel += Event.current.delta.y * ZoomStep * ZoomLevel; + + // we want to keep the _normalized_ relative position the same as before zooming + _scrollPosition = absPos - relPos * ZoomLevel; + + Event.current.Use(); + } + } + + private void HandleDragging() + { + if (Event.current.type == EventType.MouseDown) + { + _dragging = true; + _mousePosition = Event.current.mousePosition; + Event.current.Use(); + } + + if (Event.current.type == EventType.MouseUp) + { + _dragging = false; + _mousePosition = Vector2.zero; + } + + if (Event.current.type == EventType.MouseDrag) + { + var _currentMousePosition = Event.current.mousePosition; + _scrollPosition += _mousePosition - _currentMousePosition; + _mousePosition = _currentMousePosition; + } + } + + private void ApplyZoomLevel() + { + GUI.EndClip(); // window contents + GUI.EndClip(); // window itself? + GUI.matrix = Matrix4x4.TRS(new Vector3(0f, 0f, 0f), Quaternion.identity, + new Vector3(Prefs.UIScale / ZoomLevel, Prefs.UIScale / ZoomLevel, 1f)); + } + + private void ResetZoomLevel() + { + // dummies to maintain correct stack size + // TODO; figure out how to get actual clipping rects in ApplyZoomLevel(); + UI.ApplyUIScale(); + GUI.BeginClip(windowRect); + GUI.BeginClip(new Rect(0f, 0f, UI.screenWidth, UI.screenHeight)); + } + + private void DrawTopBar(Rect canvas) + { + var searchRect = canvas; + var queueRect = canvas; + searchRect.width = 200f; + queueRect.xMin += 200f + Constants.Margin; + + GUI.DrawTexture(searchRect, Assets.SlightlyDarkBackground); + GUI.DrawTexture(queueRect, Assets.SlightlyDarkBackground); + + DrawSearchBar(searchRect.ContractedBy(Constants.Margin)); + Queue.DrawQueue(queueRect.ContractedBy(Constants.Margin), !_dragging); + } + + private void DrawSearchBar(Rect canvas) + { + Profiler.Start("DrawSearchBar"); + var iconRect = new Rect( + canvas.xMax - Constants.Margin - 16f, + 0f, + 16f, + 16f) + .CenteredOnYIn(canvas); + var searchRect = new Rect( + canvas.xMin, + 0f, + canvas.width, + 30f) + .CenteredOnYIn(canvas); + + GUI.DrawTexture(iconRect, Assets.Search); + var query = Widgets.TextField(searchRect, _query); + + if (query != _query) + { + _query = query; + Find.WindowStack.FloatMenu?.Close(false); + + if (query.Length > 2) + { + // open float menu with search results, if any. + var options = new List(); + + foreach (var result in Tree.Nodes.OfType() + .Select(n => new { node = n, match = n.Matches(query) }) + .Where(result => result.match > 0) + .OrderBy(result => result.match)) + options.Add(new FloatMenuOption(result.node.Label, () => CenterOn(result.node), MenuOptionPriority.Default)); + + if (!options.Any()) + options.Add(new FloatMenuOption("Fluffy.ResearchTree.NoResearchFound".Translate(), null)); + + Find.WindowStack.Add(new FloatMenu_Fixed(options, + UI.GUIToScreenPoint( + new Vector2( + searchRect.xMin, searchRect.yMax)))); + } + } + + Profiler.End(); + } + + public void CenterOn(Node node) + { + var position = new Vector2( + (NodeSize.x + NodeMargins.x) * (node.X - .5f), + (NodeSize.y + NodeMargins.y) * (node.Y - .5f)); + + node.Highlighted = true; + + position -= new Vector2(UI.screenWidth, UI.screenHeight) / 2f; + + position.x = Mathf.Clamp(position.x, 0f, TreeRect.width - ViewRect.width); + position.y = Mathf.Clamp(position.y, 0f, TreeRect.height - ViewRect.height); + _scrollPosition = position; + } + } } \ No newline at end of file diff --git a/Source/Profiler.cs b/Source/Profiler.cs index 424e42d..1af3b7b 100644 --- a/Source/Profiler.cs +++ b/Source/Profiler.cs @@ -8,13 +8,13 @@ namespace FluffyResearchTree { public class Profiler { - [Conditional( "DEBUG" )] - public static void Start( string label = null ) + [Conditional("DEBUG")] + public static void Start(string label = null) { - DeepProfiler.Start( label ); + DeepProfiler.Start(label); } - [Conditional( "DEBUG" )] + [Conditional("DEBUG")] public static void End() { DeepProfiler.End(); diff --git a/Source/Properties/AssemblyInfo.cs b/Source/Properties/AssemblyInfo.cs index 382cb1a..d6b6c51 100644 --- a/Source/Properties/AssemblyInfo.cs +++ b/Source/Properties/AssemblyInfo.cs @@ -7,22 +7,22 @@ // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. -[assembly: AssemblyTitle( "ResearchTree" )] -[assembly: AssemblyDescription( "" )] -[assembly: AssemblyConfiguration( "" )] -[assembly: AssemblyCompany( "" )] -[assembly: AssemblyProduct( "ResearchTree" )] -[assembly: AssemblyCopyright( "Copyright © 2015" )] -[assembly: AssemblyTrademark( "" )] -[assembly: AssemblyCulture( "" )] +[assembly: AssemblyTitle("ResearchTree")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ResearchTree")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible( false )] +[assembly: ComVisible(false)] // The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid( "3eb50a4a-c426-40e5-b682-458b34007336" )] +[assembly: Guid("3eb50a4a-c426-40e5-b682-458b34007336")] // Version information for an assembly consists of the following four values: // diff --git a/Source/Queue/Queue.cs b/Source/Queue/Queue.cs index 81f7d9c..b41e1ec 100644 --- a/Source/Queue/Queue.cs +++ b/Source/Queue/Queue.cs @@ -1,11 +1,10 @@ // Queue.cs // Copyright Karel Kroeze, 2020-2020 -//using Multiplayer.API; -using System.Collections.Generic; -using System.Linq; using RimWorld; using RimWorld.Planet; +using System.Collections.Generic; +using System.Linq; using UnityEngine; using Verse; using static FluffyResearchTree.Assets; @@ -15,11 +14,11 @@ namespace FluffyResearchTree { public class Queue : WorldComponent { - private static Queue _instance; - private readonly List _queue = new List(); - private List _saveableQueue; + private static Queue _instance; + private readonly List _queue = new List(); + private List _saveableQueue; - public Queue( World world ) : base( world ) + public Queue(World world) : base(world) { _instance = this; } @@ -28,56 +27,43 @@ public Queue( World world ) : base( world ) /// Removes and returns the first node in the queue. /// /// - public static ResearchNode Pop - { - get - { - if ( _instance._queue != null && _instance._queue.Count > 0 ) - { - var node = _instance._queue[0]; - _instance._queue.RemoveAt( 0 ); - return node; - } - - return null; - } - } + /// public static int NumQueued => _instance._queue.Count - 1; - public static void TryDequeue( ResearchNode node ) + public static void TryDequeue(ResearchNode node) { - if ( _instance._queue.Contains( node ) ) - Dequeue( node ); + if (_instance._queue.Contains(node)) + Dequeue(node); } -// [SyncMethod] - public static void Dequeue( ResearchNode node ) + // [SyncMethod] + public static void Dequeue(ResearchNode node) { // remove this node - _instance._queue.Remove( node ); + _instance._queue.Remove(node); // remove all nodes that depend on it - var followUps = _instance._queue.Where( n => n.GetMissingRequiredRecursive().Contains( node ) ).ToList(); - foreach ( var followUp in followUps ) - _instance._queue.Remove( followUp ); + var followUps = _instance._queue.Where(n => n.GetMissingRequiredRecursive().Contains(node)).ToList(); + foreach (var followUp in followUps) + _instance._queue.Remove(followUp); // if currently researching this node, stop that - if ( Find.ResearchManager.currentProj == node.Research ) + if (Find.ResearchManager.currentProj == node.Research) Find.ResearchManager.currentProj = null; } - public static void DrawLabels( Rect visibleRect ) + public static void DrawLabels(Rect visibleRect) { - Profiler.Start( "Queue.DrawLabels" ); + Profiler.Start("Queue.DrawLabels"); var i = 1; - foreach ( var node in _instance._queue ) + foreach (var node in _instance._queue) { - if ( node.IsVisible( visibleRect ) ) + if (node.IsVisible(visibleRect)) { - var main = ColorCompleted[node.Research.techLevel]; + var main = ColorCompleted[node.Research.techLevel]; var background = i > 1 ? ColorUnavailable[node.Research.techLevel] : main; - DrawLabel( node.QueueRect, main, background, i ); + DrawLabel(node.QueueRect, main, background, i); } i++; @@ -86,100 +72,100 @@ public static void DrawLabels( Rect visibleRect ) Profiler.End(); } - public static void DrawLabel( Rect canvas, Color main, Color background, int label ) + public static void DrawLabel(Rect canvas, Color main, Color background, int label) { // draw coloured tag GUI.color = main; - GUI.DrawTexture( canvas, CircleFill ); + GUI.DrawTexture(canvas, CircleFill); // if this is not first in line, grey out centre of tag - if ( background != main ) + if (background != main) { GUI.color = background; - GUI.DrawTexture( canvas.ContractedBy( 2f ), CircleFill ); + GUI.DrawTexture(canvas.ContractedBy(2f), CircleFill); } // draw queue number - GUI.color = Color.white; + GUI.color = Color.white; Text.Anchor = TextAnchor.MiddleCenter; - Widgets.Label( canvas, label.ToString() ); + Widgets.Label(canvas, label.ToString()); Text.Anchor = TextAnchor.UpperLeft; } - public static void Enqueue( ResearchNode node, bool add ) + public static void Enqueue(ResearchNode node, bool add) { - Log.Debug( $"Enqueuing: {node.Research.defName}" ); + Log.Debug($"Enqueuing: {node.Research.defName}"); // if we're not adding, clear the current queue and current research project - if ( !add ) + if (!add) { _instance._queue.Clear(); Find.ResearchManager.currentProj = null; } // add to the queue if not already in it - if ( !_instance._queue.Contains( node ) ) - _instance._queue.Add( node ); + if (!_instance._queue.Contains(node)) + _instance._queue.Add(node); // try set the first research in the queue to be the current project. var next = _instance._queue.First(); Find.ResearchManager.currentProj = next?.Research; // null if next is null. } -// [SyncMethod] - public static void EnqueueRange( IEnumerable nodes, bool add ) + // [SyncMethod] + public static void EnqueueRange(IEnumerable nodes, bool add) { - TutorSystem.Notify_Event( "StartResearchProject" ); + TutorSystem.Notify_Event("StartResearchProject"); // clear current Queue if not adding - if ( !add ) + if (!add) { _instance._queue.Clear(); Find.ResearchManager.currentProj = null; } // sorting by depth ensures prereqs are met - cost is just a bonus thingy. - foreach ( var node in nodes.OrderBy( node => node.X ).ThenBy( node => node.Research.CostApparent ) ) - Enqueue( node, true ); + foreach (var node in nodes.OrderBy(node => node.X).ThenBy(node => node.Research.CostApparent)) + Enqueue(node, true); } - public static bool IsQueued( ResearchNode node ) + public static bool IsQueued(ResearchNode node) { - return _instance._queue.Contains( node ); + return _instance._queue.Contains(node); } - public static void TryStartNext( ResearchProjectDef finished ) + public static void TryStartNext(ResearchProjectDef finished) { var current = _instance._queue.FirstOrDefault()?.Research; - Log.Debug( "TryStartNext: current; {0}, finished; {1}", current, finished ); - if ( finished != _instance._queue.FirstOrDefault()?.Research ) + Log.Debug("TryStartNext: current; {0}, finished; {1}", current, finished); + if (finished != _instance._queue.FirstOrDefault()?.Research) { - TryDequeue( finished ); + TryDequeue(finished); return; } - _instance._queue.RemoveAt( 0 ); + _instance._queue.RemoveAt(0); var next = _instance._queue.FirstOrDefault()?.Research; - Log.Debug( "TryStartNext: next; {0}", next ); + Log.Debug("TryStartNext: next; {0}", next); Find.ResearchManager.currentProj = next; - DoCompletionLetter( current, next ); + DoCompletionLetter(current, next); } - private static void DoCompletionLetter( ResearchProjectDef current, ResearchProjectDef next ) + private static void DoCompletionLetter(ResearchProjectDef current, ResearchProjectDef next) { // message - string label = "ResearchFinished".Translate( current.LabelCap ); - string text = current.LabelCap + "\n\n" + current.description; + string label = "ResearchFinished".Translate(current.LabelCap); + string text = current.LabelCap + "\n\n" + current.description; - if ( next != null ) + if (next != null) { - text += "\n\n" + "Fluffy.ResearchTree.NextInQueue".Translate( next.LabelCap ); - Find.LetterStack.ReceiveLetter( label, text, LetterDefOf.PositiveEvent ); + text += "\n\n" + "Fluffy.ResearchTree.NextInQueue".Translate(next.LabelCap); + Find.LetterStack.ReceiveLetter(label, text, LetterDefOf.PositiveEvent); } else { - text += "\n\n" + "Fluffy.ResearchTree.NextInQueue".Translate( "Fluffy.ResearchTree.None".Translate() ); - Find.LetterStack.ReceiveLetter( label, text, LetterDefOf.NeutralEvent ); + text += "\n\n" + "Fluffy.ResearchTree.NextInQueue".Translate("Fluffy.ResearchTree.None".Translate()); + Find.LetterStack.ReceiveLetter(label, text, LetterDefOf.NeutralEvent); } } @@ -188,57 +174,57 @@ public override void ExposeData() base.ExposeData(); // store research defs as these are the defining elements - if ( Scribe.mode == LoadSaveMode.Saving ) - _saveableQueue = _queue.Select( node => node.Research ).ToList(); + if (Scribe.mode == LoadSaveMode.Saving) + _saveableQueue = _queue.Select(node => node.Research).ToList(); - Scribe_Collections.Look( ref _saveableQueue, "Queue", LookMode.Def ); + Scribe_Collections.Look(ref _saveableQueue, "Queue", LookMode.Def); - if ( Scribe.mode == LoadSaveMode.PostLoadInit ) + if (Scribe.mode == LoadSaveMode.PostLoadInit) // initialize the queue - foreach ( var research in _saveableQueue ) + foreach (var research in _saveableQueue) { // find a node that matches the research - or null if none found var node = research.ResearchNode(); // enqueue the node - if ( node != null ) + if (node != null) { - Log.Debug( "Adding {0} to queue", node.Research.LabelCap ); - Enqueue( node, true ); + Log.Debug("Adding {0} to queue", node.Research.LabelCap); + Enqueue(node, true); } else { - Log.Debug( "Could not find node for {0}", research.LabelCap ); + Log.Debug("Could not find node for {0}", research.LabelCap); } } } - public static void DrawQueue( Rect canvas, bool interactible ) + public static void DrawQueue(Rect canvas, bool interactible) { - Profiler.Start( "Queue.DrawQueue" ); - if ( !_instance._queue.Any() ) + Profiler.Start("Queue.DrawQueue"); + if (!_instance._queue.Any()) { Text.Anchor = TextAnchor.MiddleCenter; - GUI.color = TechLevelColor; - Widgets.Label( canvas, "Fluffy.ResearchTree.NothingQueued".Translate() ); + GUI.color = TechLevelColor; + Widgets.Label(canvas, "Fluffy.ResearchTree.NothingQueued".Translate()); Text.Anchor = TextAnchor.UpperLeft; - GUI.color = Color.white; + GUI.color = Color.white; return; } var pos = canvas.min; - for ( var i = 0; i < _instance._queue.Count && pos.x + NodeSize.x < canvas.xMax; i++ ) + for (var i = 0; i < _instance._queue.Count && pos.x + NodeSize.x < canvas.xMax; i++) { var node = _instance._queue[i]; var rect = new Rect( - pos.x - Margin, - pos.y - Margin, + pos.x - Margin, + pos.y - Margin, NodeSize.x + 2 * Margin, NodeSize.y + 2 * Margin ); - node.DrawAt( pos, rect, true ); - if ( interactible && Mouse.IsOver( rect ) ) - MainTabWindow_ResearchTree.Instance.CenterOn( node ); + node.DrawAt(pos, rect, true); + if (interactible && Mouse.IsOver(rect)) + MainTabWindow_ResearchTree.Instance.CenterOn(node); pos.x += NodeSize.x + Margin; } @@ -247,9 +233,9 @@ public static void DrawQueue( Rect canvas, bool interactible ) public static void Notify_InstantFinished() { - foreach ( var node in new List( _instance._queue ) ) - if ( node.Research.IsFinished ) - TryDequeue( node ); + foreach (var node in new List(_instance._queue)) + if (node.Research.IsFinished) + TryDequeue(node); Find.ResearchManager.currentProj = _instance._queue.FirstOrDefault()?.Research; } diff --git a/Source/Queue/Queue_HarmonyPatches.cs b/Source/Queue/Queue_HarmonyPatches.cs index 8588792..8b0463c 100644 --- a/Source/Queue/Queue_HarmonyPatches.cs +++ b/Source/Queue/Queue_HarmonyPatches.cs @@ -9,37 +9,29 @@ namespace FluffyResearchTree { public class HarmonyPatches_Queue { - [HarmonyPatch( typeof( ResearchManager ), "ResearchPerformed", typeof( float ), typeof( Pawn ) )] - public class ResearchPerformed + [HarmonyPatch(typeof(ResearchManager), "FinishProject")] + public class DoCompletionDialog { - // check if last active project was finished. If so, try start the next project. - // Thanks to NotFood for this nice simplification, I've adapted his/her code; - // https://github.com/notfood/RimWorld-ResearchPal/blob/master/Source/Injectors/ResearchManagerPatch.cs - private static void Prefix( ResearchManager __instance, ref ResearchProjectDef __state ) + //If Semi Random Research mod is not loaded, suppress vanilla completion dialog. + [System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "It is used, ignore compiler messages")] + private static void Prefix(ref bool doCompletionDialog) { - __state = __instance.currentProj; - Log.Debug( "{0} progress: {1}", __state.LabelCap, __state.ProgressPercent ); - } - - private static void Postfix( ResearchProjectDef __state ) - { - Log.Debug( "{0} finished?: {1}", __state, __state?.IsFinished ); - if ( __state?.IsFinished ?? false ) + if (ModLister.GetActiveModWithIdentifier("CaptainMuscles.SemiRandomResearch") == null) { - Log.Debug( "{0} finished", __state.LabelCap ); - Queue.TryStartNext( __state ); + doCompletionDialog = false; } } - } - [HarmonyPatch( typeof( ResearchManager ), "FinishProject" )] - public class DoCompletionDialog - { - // suppress vanilla completion dialog, we never want to show it. - private static void Prefix( ref bool doCompletionDialog ) + + [System.Diagnostics.CodeAnalysis.SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "It is used, ignore compiler messages")] + private static void Postfix(ResearchProjectDef proj) { - doCompletionDialog = false; + if (proj.IsFinished) + { + Log.Debug("Patch of FinishProject: {0} finished", proj.label); + Queue.TryStartNext(proj); + } } } } -} \ No newline at end of file +} diff --git a/Source/ResearchTree.cs b/Source/ResearchTree.cs index ad40728..d48ec0d 100644 --- a/Source/ResearchTree.cs +++ b/Source/ResearchTree.cs @@ -1,22 +1,20 @@ // ResearchTree.cs // Copyright Karel Kroeze, 2020-2020 -using System.Reflection; using HarmonyLib; +using System.Reflection; using Verse; -//using Multiplayer.API; namespace FluffyResearchTree { public class ResearchTree : Mod { - public ResearchTree( ModContentPack content ) : base( content ) + public ResearchTree(ModContentPack content) : base(content) { - var harmony = new Harmony( "Fluffy.ResearchTree" ); - harmony.PatchAll( Assembly.GetExecutingAssembly() ); + var harmony = new Harmony("Fluffy.ResearchTree"); + harmony.PatchAll(Assembly.GetExecutingAssembly()); + -// if ( MP.enabled ) -// MP.RegisterAll(); } } } \ No newline at end of file diff --git a/Source/ResearchTree.csproj b/Source/ResearchTree.csproj index 02fbea8..891e909 100644 --- a/Source/ResearchTree.csproj +++ b/Source/ResearchTree.csproj @@ -1,103 +1,115 @@ - - - - - Debug - AnyCPU - {3EB50A4A-C426-40E5-B682-458B34007336} - Library - Properties - ResearchTree - ResearchTree - v4.7.2 - 512 - - - - false - none - false - ..\Assemblies\ - DEBUG - prompt - 4 - true - false - - - none - true - ..\Assemblies\ - - - prompt - 4 - true - false - - - - packages\Lib.Harmony.2.0.0.8\lib\net472\0Harmony.dll - False - - - C:\Program Files (x86)\Steam\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\Assembly-CSharp.dll - False - - - - - - - - - - C:\Program Files (x86)\Steam\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll - False - - - C:\Program Files (x86)\Steam\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.IMGUIModule.dll - False - - - C:\Program Files (x86)\Steam\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.TextRenderingModule.dll - False - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - mod update -x - - + + + + + Debug + AnyCPU + {3EB50A4A-C426-40E5-B682-458B34007336} + Library + Properties + ResearchTree + ResearchTree + v4.7.2 + 512 + + + + + + false + none + false + ..\Assemblies\ + DEBUG + prompt + 4 + true + false + + + none + true + ..\Assemblies\ + + + prompt + 4 + true + false + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + packages\Lib.Harmony.2.2.2\lib\net472\0Harmony.dll + + + C:\Program Files (x86)\Steam\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\Assembly-CSharp.dll + False + + + + + packages\System.ValueTuple.4.5.0\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll + + + + + + + C:\Program Files (x86)\Steam\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.CoreModule.dll + False + + + C:\Program Files (x86)\Steam\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.IMGUIModule.dll + False + + + C:\Program Files (x86)\Steam\steamapps\common\RimWorld\RimWorldWin64_Data\Managed\UnityEngine.TextRenderingModule.dll + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/Source/packages.config b/Source/packages.config index 206f997..45d21c7 100644 --- a/Source/packages.config +++ b/Source/packages.config @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file