@@ -3,10 +3,12 @@ import { time, TimestampStyles, type User } from "discord.js";
33import type { BotContext } from "@/context.js" ;
44import type { MessageCommand } from "@/commands/command.js" ;
55import type { ProcessableMessage } from "@/service/command.js" ;
6+ import type { Penis } from "@/storage/db/model.js" ;
67
78import * as penis from "@/storage/penis.js" ;
89import 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" ;
1012
1113export type Radius = 0 | 1 | 2 | 3 ;
1214
@@ -17,8 +19,8 @@ const RADIUS_CHARS: Record<Radius, string> = {
1719 3 : "≡" ,
1820} ;
1921
20- const PENIS_MAX = 30 ;
21- const RADIUS_MAX = 3 ;
22+ const PENIS_LENGTH_MAX = 30 ;
23+ const PENIS_RADIUS_MAX = 3 ;
2224
2325const sendPenis = async (
2426 user : User ,
@@ -28,11 +30,11 @@ const sendPenis = async (
2830 measurement : Date = new Date ( ) ,
2931) : Promise < void > => {
3032 const radiusChar = RADIUS_CHARS [ radius ] ;
31- const penis = `8${ radiusChar . repeat ( size ) } D` ;
33+ const penis = `8${ radiusChar . repeat ( size | 0 ) } D` ;
3234 const circumference = ( Math . PI * radius * 2 ) . toFixed ( 2 ) ;
3335
3436 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 ) } )` ,
3638 ) ;
3739} ;
3840
@@ -41,6 +43,27 @@ const isNewLongestDick = async (size: number): Promise<boolean> => {
4143 return oldLongest < size ;
4244} ;
4345
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 ) ;
4467/**
4568 * Penis command. Displays the users penis length
4669 */
@@ -104,40 +127,43 @@ export default class PenisCommand implements MessageCommand {
104127 const userToMeasure = mention !== undefined ? mention : author ;
105128
106129 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+ ) ;
134134
135135 await sendPenis (
136136 userToMeasure ,
137137 message ,
138- recentMeasurement . size ,
139- recentMeasurement . radius ,
140- new Date ( recentMeasurement . measuredAt ) ,
138+ measurement . size ,
139+ measurement . radius ,
140+ new Date ( measurement . measuredAt ) ,
141141 ) ;
142142 }
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+ }
143169}
0 commit comments