1 /*
2     Copyright © 2020, Luna Nielsen
3     Distributed under the 2-Clause BSD License, see LICENSE file.
4     
5     Authors: Luna Nielsen
6 */
7 module engine.vn.character;
8 import engine.vn.render;
9 import engine;
10 import std.format;
11 
12 /**
13     Character declaration
14 */
15 struct CharDecl {
16     /**
17         The symbolic name for the character
18     */
19     string name;
20 
21     /**
22         Name of character
23     */
24     dstring[string] names;
25 
26     /**
27         Textures for character
28     */
29     string[string] textures;
30 }
31 
32 /**
33     Character registry
34 */
35 class CharacterRegistry {
36 private static:
37     CharDecl[string] registry;
38 
39 public static:
40     /**
41         Register a character
42     */
43     public void register(string name, CharDecl declaration) {
44         registry[name] = declaration;
45     }
46 
47     /**
48         Create a character from its name
49     */
50     public Character create(string name) {
51         return new Character(registry[name]);
52     }
53 }
54 
55 /**
56     A character in the visual novel
57 */
58 class Character {
59 private:
60 
61     /**
62         The system name of the character
63     */
64     string name;
65 
66     /**
67         All the names the character can have
68     */
69     dstring[string] dnames;
70 
71     /**
72         Named textures for the character
73     */
74     AtlasIndex[string] textures;
75 
76     /**
77         The texture of the character
78     */
79     string texture;
80 
81     /**
82         Cached texture size
83     */
84     vec2 textureSize;
85 
86     /**
87         How visible the character is
88     */
89     float visibility = 0;
90 
91 public:
92 
93     dstring displayName() {
94         return dnames[CurrentLanguage];
95     }
96 
97     /**
98         Whether to show the character
99     */
100     bool shown;
101 
102     /**
103         Position of the character on the screen
104     */
105     vec2 position;
106 
107     /**
108         Whether the character's texture is flipped vertically
109     */
110     bool isFlipped;
111 
112     /**
113         The current texture
114     */
115     AtlasIndex* currentTexture() {
116         return texture in textures;
117     }
118 
119     /**
120         Sets the character's texture
121     */
122     void setTexture(string texture) {
123         this.texture = texture;
124         this.textureSize = currentTexture.area.zw;
125     }
126 
127     /**
128         Creates a character from a definition
129     */
130     this(CharDecl decl) {
131         this.name = decl.name;
132         this.dnames = decl.names;
133         foreach (texName, texture; decl.textures) {
134             AppLog.info("VN Engine", "Added character texture %s", texName);
135             GameAtlas.add(genTexName(name, texName), texture);
136             textures[texName] = GameAtlas[genTexName(name, texName)];
137         }
138 
139         // Set texture to neutral if any was given for the character
140         if (decl.textures.length > 0) this.setTexture("neutral");
141     }
142 
143     /**
144         Clean up textures between scene switches.
145     */
146     ~this() {
147 
148         // Remove unused textures from the atlas
149         foreach(texName, _; textures) {
150             GameAtlas.remove(genTexName(name, texName));
151         }
152     }
153 
154     /**
155         Activates the character causing them to jump if visible
156     */
157     private float activationTimer = 0.0;
158     private float jump = 0;
159     void activate() {
160         activationTimer = 1;
161     }
162 
163     void update() {
164 
165         // Handle visibility fade
166         if (shown) {
167             if (visibility < 1) visibility += 10*deltaTime();
168             if (visibility > 1) visibility = 1;
169         } else {
170             if (visibility > 0) visibility -= 10*deltaTime();
171             if (visibility < 0) visibility = 0;
172         }
173 
174         // TODO: use the animation system for this
175 
176         // Handle activation timer countdown
177         if (activationTimer > 0) activationTimer -= 5*deltaTime();
178         if (activationTimer < 0) activationTimer = 0;
179 
180         if (activationTimer > 0.5) {
181             float t = (0.5-activationTimer)*2;
182             jump = lerp!float(-12, 0, t); 
183         } else {
184             float t = (0.5-activationTimer)*2;
185             jump = lerp!float(0, -12, t); 
186         }
187     }
188 
189     void draw() {
190         if (!shown && visibility == 0) return;
191         if (currentTexture is null) return;
192         GameBatch.draw(
193             *currentTexture, 
194             vec4(position.x, position.y-jump, textureSize.x, textureSize.y), 
195             vec4.init, 
196             vec2(textureSize.x/2, textureSize.y),
197             0,
198             isFlipped ? SpriteFlip.Horizontal : SpriteFlip.None,
199             vec4(1, 1, 1, visibility)
200         );
201     }
202 }
203 
204 private string genTexName(string charName, string texName) {
205     return "%s/%s".format(charName, texName);
206 }