Merge remote branch 'origin/fruitiex/fruitbalance'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / bgmscript.qc
1 #define CONSTANT_SPEED_DECAY
2
3 float bgmscriptbuf;
4 float bgmscriptbufsize;
5 float bgmscriptbufloaded;
6
7 .float bgmscriptline;
8 .float bgmscriptline0;
9 .float bgmscriptvolume;
10 .float bgmscripttime;
11 .float bgmscriptstate;
12 .float bgmscriptstatetime;
13
14 float GetAttackDecaySustainAmplitude(float a, float d, float s, float t)
15 {
16         // phase:
17         //   attack: from 0 to 1, in time a for a full length
18         //   decay: from 1 to s, in time d
19         //   sustain: s
20         
21         if(t < 0)
22                 return 0;
23         
24         if(a)
25                 if(t <= a)
26                         return t / a;
27
28         if(d)
29                 if(t <= a + d)
30                         return ((t - a) / d) * (s - 1) + 1;
31
32         return s;
33 }
34
35 float GetReleaseAmplitude(float d, float s, float r, float t)
36 {
37         float decayval, releaseval;
38
39         if(!r)
40                 return 0;
41         
42         if(t > r)
43                 return 0;
44         
45         releaseval = s * (1 - t / r);
46
47         if(t < -d)
48                 return 1;
49
50         if(t < 0 && t >= -d)
51         {
52                 // pre-time decay
53                 // value is s at time  0
54                 //          1 at time -d
55                 decayval = ((t + d) / d) * (s - 1) + 1;
56                 return max(decayval, releaseval);
57         }
58
59         return releaseval;
60 }
61
62 float GetAttackTime(float a, float amp)
63 {
64         return amp * a;
65 }
66
67 float GetReleaseTime(float d, float s, float r, float amp)
68 {
69         float decaytime, releasetime;
70
71         if(!s)
72                 return 0;
73         
74         // if amp > s, we may be in the attack or in the prolonged decay curve
75         releasetime = (1 - amp / s) * r;
76
77         if(amp > s)
78         {
79                 if(s == 1) // gracefully handle division by zero here
80                         return 0;
81
82                 // pre-time decay
83                 // value is s at time  0
84                 //          1 at time -d
85                 decaytime = (amp - 1) / (s - 1) * d - d;
86                 return max(decaytime, releasetime);
87         }
88
89         return releasetime;
90 }
91
92 void BGMScript_Init()
93 {
94         string s;
95         float fh;
96         bgmscriptbuf = bgmscriptbufsize = 0;
97         bgmscriptbufloaded = 1;
98         s = strcat("maps/", mi_shortname, ".bgs");
99         fh = fopen(s, FILE_READ);
100         if(fh < 0)
101                 return;
102         bgmscriptbuf = buf_create();
103         while((s = fgets(fh)))
104         {
105                 bufstr_set(bgmscriptbuf, bgmscriptbufsize, s);
106                 ++bgmscriptbufsize;
107         }
108         fclose(fh);
109 }
110
111 void BGMScript_InitEntity(entity e)
112 {
113         float l;
114         string m;
115         if(e.bgmscript != "")
116         {
117                 if(!bgmscriptbufloaded)
118                         BGMScript_Init();
119
120                 float i;
121
122                 m = strcat(e.bgmscript, " ");
123                 l = strlen(m);
124
125                 e.bgmscriptline0 = -1;
126                 for(i = 0; i < bgmscriptbufsize; ++i)
127                 {
128                         if(substring(bufstr_get(bgmscriptbuf, i), 0, l) == m)
129                                 break;
130                 }
131                 e.bgmscriptline = e.bgmscriptline0 = i;
132                 if(i >= bgmscriptbufsize)
133                 {
134                         print("ERROR: bgmscript does not define ", e.bgmscript, "\n");
135                         e.bgmscript = "";
136                 }
137         }
138 }
139
140 float GetCurrentAmplitude(entity e, float trel)
141 {
142         if(e.bgmscriptstate)
143                 return GetAttackDecaySustainAmplitude(e.bgmscriptattack, e.bgmscriptdecay, e.bgmscriptsustain, trel) * e.bgmscriptvolume;
144         else
145         {
146 #ifdef CONSTANT_SPEED_DECAY
147                 return GetReleaseAmplitude(e.bgmscriptdecay, e.bgmscriptsustain * e.bgmscriptvolume, e.bgmscriptrelease, trel);
148 #else
149                 return GetReleaseAmplitude(e.bgmscriptdecay, e.bgmscriptsustain, e.bgmscriptrelease, trel) * e.bgmscriptvolume;
150 #endif
151         }
152 }
153
154 float GetTimeForAmplitude(entity e, float amp)
155 {
156         if(e.bgmscriptstate)
157                 return GetAttackTime(e.bgmscriptattack, amp / e.bgmscriptvolume);
158         else
159         {
160 #ifdef CONSTANT_SPEED_DECAY
161                 return GetReleaseTime(e.bgmscriptdecay, e.bgmscriptsustain * e.bgmscriptvolume, e.bgmscriptrelease, amp);
162 #else
163                 return GetReleaseTime(e.bgmscriptdecay, e.bgmscriptsustain, e.bgmscriptrelease, amp / e.bgmscriptvolume);
164 #endif
165         }
166 }
167
168 float BGMScript(entity e)
169 {
170         float t;
171         float amp, vel;
172
173         if(e.bgmscript == "")
174                 return 1;
175         
176         if(cvar("bgmvolume") <= 0)
177                 return -1;
178
179         e.just_toggled = FALSE;
180
181         t = gettime(GETTIME_CDTRACK);
182         if(t < 0)
183                 return -1;
184
185         if(t < e.bgmscripttime)
186         {
187                 //print("reset ", e.bgmscript, "\n");
188                 amp = GetCurrentAmplitude(e, e.bgmscripttime - e.bgmscriptstatetime + drawframetime);
189
190                 e.bgmscriptline = e.bgmscriptline0;
191                 e.bgmscripttime = t;
192
193                 // treat this as a stop event for all notes, to prevent sticking keys
194                 e.bgmscriptstate = FALSE;
195                 e.bgmscriptvolume = 1;
196                 e.bgmscriptstatetime = t - GetTimeForAmplitude(e, amp);
197         }
198
199         // find the CURRENT line
200         for(;;)
201         {
202                 tokenize_console(bufstr_get(bgmscriptbuf, e.bgmscriptline));
203                 if(stof(argv(1)) >= t || argv(0) != e.bgmscript)
204                 {
205                         e.bgmscripttime = t;
206                         return GetCurrentAmplitude(e, t - e.bgmscriptstatetime);
207                 }
208                 else if(t >= stof(argv(1)))
209                 {
210                         e.bgmscriptline += 1;
211                         e.bgmscripttime = stof(argv(1));
212
213                         amp = GetCurrentAmplitude(e, e.bgmscripttime - e.bgmscriptstatetime);
214
215                         // time code reached!
216                         vel = stof(argv(2));
217                         if(vel > 0)
218                         {
219                                 e.just_toggled = e.bgmscriptstate = TRUE;
220                                 e.bgmscriptvolume = vel;
221                         }
222                         else
223                                 e.just_toggled = e.bgmscriptstate = FALSE;
224
225                         e.bgmscriptstatetime = e.bgmscripttime - GetTimeForAmplitude(e, amp);
226                 }
227         }
228 }
229