@@ -3,10 +3,12 @@ import { time, TimestampStyles, type User } from "discord.js";
3
3
import type { BotContext } from "@/context.js" ;
4
4
import type { MessageCommand } from "@/commands/command.js" ;
5
5
import type { ProcessableMessage } from "@/service/command.js" ;
6
+ import type { Penis } from "@/storage/db/model.js" ;
6
7
7
8
import * as penis from "@/storage/penis.js" ;
8
9
import log from "@log" ;
9
- import { randomValue } from "@/service/random.js" ;
10
+ import { NormalDistribution , RandomNumberGenerator , SecureRandomSource } from "@/service/random.js" ;
11
+ import { clamp } from "@/utils/math.js" ;
10
12
11
13
export type Radius = 0 | 1 | 2 | 3 ;
12
14
@@ -17,8 +19,8 @@ const RADIUS_CHARS: Record<Radius, string> = {
17
19
3 : "≡" ,
18
20
} ;
19
21
20
- const PENIS_MAX = 30 ;
21
- const RADIUS_MAX = 3 ;
22
+ const PENIS_LENGTH_MAX = 30 ;
23
+ const PENIS_RADIUS_MAX = 3 ;
22
24
23
25
const sendPenis = async (
24
26
user : User ,
@@ -28,11 +30,11 @@ const sendPenis = async (
28
30
measurement : Date = new Date ( ) ,
29
31
) : Promise < void > => {
30
32
const radiusChar = RADIUS_CHARS [ radius ] ;
31
- const penis = `8${ radiusChar . repeat ( size ) } D` ;
33
+ const penis = `8${ radiusChar . repeat ( size | 0 ) } D` ;
32
34
const circumference = ( Math . PI * radius * 2 ) . toFixed ( 2 ) ;
33
35
34
36
await message . reply (
35
- `Pimmel von <@ ${ user . id } > :\n${ penis } \n(Länge: ${ size } cm, Umfang: ${ circumference } cm, Gemessen um ${ time ( measurement , TimestampStyles . LongDateTime ) } )` ,
37
+ `Pimmel von ${ user } :\n${ penis } \n(Länge: ${ size . toFixed ( 2 ) } cm, Umfang: ${ circumference } cm, Gemessen um ${ time ( measurement , TimestampStyles . LongDateTime ) } )` ,
36
38
) ;
37
39
} ;
38
40
@@ -41,6 +43,27 @@ const isNewLongestDick = async (size: number): Promise<boolean> => {
41
43
return oldLongest < size ;
42
44
} ;
43
45
46
+ const randomSource = new SecureRandomSource ( ) ;
47
+
48
+ const lengthDistribution = new NormalDistribution (
49
+ 14.65 , // chatgpt: μ ≈ 14.5 to 14.8 cm
50
+ 1.85 , // chatgpt: σ ≈ 1.7 to 2.0 cm
51
+ ) ;
52
+
53
+ /**
54
+ * ChatGPT emits these values for circumference:
55
+ * - μ ≈ 11.7 to 12.0 cm
56
+ * - σ ≈ 1.0 cm (estimated via studies like Veale et al.)
57
+ *
58
+ * -> we use (11.7 cm + 12.0 cm)/2 = 11.85 cm as circumference
59
+ * -> radius = circumference / (2*pi)
60
+ *
61
+ * We do the same for σ.
62
+ */
63
+ const radiusDistribution = new NormalDistribution ( 11.85 / ( Math . PI * 2 ) , 1 / ( Math . PI * 2 ) ) ;
64
+
65
+ const sizeGenerator = new RandomNumberGenerator ( lengthDistribution , randomSource ) ;
66
+ const radiusGenerator = new RandomNumberGenerator ( radiusDistribution , randomSource ) ;
44
67
/**
45
68
* Penis command. Displays the users penis length
46
69
*/
@@ -104,40 +127,43 @@ export default class PenisCommand implements MessageCommand {
104
127
const userToMeasure = mention !== undefined ? mention : author ;
105
128
106
129
log . debug ( `${ author . id } wants to measure penis of user ${ userToMeasure . id } ` ) ;
107
-
108
- const recentMeasurement = await penis . fetchRecentMeasurement ( userToMeasure ) ;
109
-
110
- if ( recentMeasurement === undefined ) {
111
- log . debug ( `No recent measuring of ${ userToMeasure . id } found. Creating Measurement` ) ;
112
-
113
- const size =
114
- userToMeasure . id === context . client . user . id
115
- ? PENIS_MAX
116
- : randomValue ( { min : 1 , maxInclusive : PENIS_MAX } ) ;
117
- const radius : Radius =
118
- userToMeasure . id === context . client . user . id
119
- ? RADIUS_MAX
120
- : size === 0
121
- ? ( 0 as Radius )
122
- : ( randomValue ( { min : 1 , maxInclusive : RADIUS_MAX } ) as Radius ) ;
123
-
124
- if ( await isNewLongestDick ( size ) ) {
125
- log . debug ( `${ userToMeasure } has the new longest dick with size ${ size } ` ) ;
126
- }
127
-
128
- await Promise . all ( [
129
- penis . insertMeasurement ( userToMeasure , size , radius ) ,
130
- sendPenis ( userToMeasure , message , size , radius ) ,
131
- ] ) ;
132
- return ;
133
- }
130
+ const measurement = await this . #getOrCreateMeasurement(
131
+ userToMeasure ,
132
+ userToMeasure . id === context . client . user . id ,
133
+ ) ;
134
134
135
135
await sendPenis (
136
136
userToMeasure ,
137
137
message ,
138
- recentMeasurement . size ,
139
- recentMeasurement . radius ,
140
- new Date ( recentMeasurement . measuredAt ) ,
138
+ measurement . size ,
139
+ measurement . radius ,
140
+ new Date ( measurement . measuredAt ) ,
141
141
) ;
142
142
}
143
+
144
+ async #getOrCreateMeasurement( userToMeasure : User , hasLongest : boolean ) : Promise < Penis > {
145
+ const recentMeasurement = await penis . fetchRecentMeasurement ( userToMeasure ) ;
146
+ if ( recentMeasurement !== undefined ) {
147
+ return recentMeasurement ;
148
+ }
149
+
150
+ log . debug ( `No recent measuring of ${ userToMeasure . id } found. Creating Measurement` ) ;
151
+
152
+ const size = hasLongest
153
+ ? PENIS_LENGTH_MAX
154
+ : clamp ( sizeGenerator . get ( ) , 1 , PENIS_LENGTH_MAX ) ; // TODO: Do we really want to clamp here? Maybe just clamp(v, 1, Infinity)?
155
+
156
+ const radiusRaw = hasLongest
157
+ ? PENIS_RADIUS_MAX
158
+ : clamp ( radiusGenerator . get ( ) , 1 , PENIS_RADIUS_MAX ) ; // TODO: Do we really want to clamp here? Maybe just clamp(v, 1, Infinity)?
159
+
160
+ // TODO: Maye we want the radius to be integer only for display (and keep the float internally)
161
+ const radius = clamp ( Math . round ( radiusRaw ) , 1 , PENIS_RADIUS_MAX ) as Radius ;
162
+
163
+ if ( await isNewLongestDick ( size ) ) {
164
+ log . debug ( `${ userToMeasure } has the new longest dick with size ${ size } ` ) ;
165
+ }
166
+
167
+ return await penis . insertMeasurement ( userToMeasure , size , radius ) ;
168
+ }
143
169
}
0 commit comments